In my project I need container that holds smart pointers to an data unit instances. I write the class (simple example):
template <typename T>
class Queue
{
public:
void push(const T & param)
{
m_deque.push_front(param);
}
private:
std::deque<T> m_deque;
};
Than I want to push the one:
int main()
{
Queue< std::unique_ptr<DataBox> > queue;
std::unique_ptr<DataBox> d1(new DataBox(11));
queue.push(d1);
return 0;
}
And VS2017 compiler says that I can't do this:
Error C2280 std::unique_ptr<DataBox,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)': attempting to reference a deleted function
As I understand the reason of the error is attempt to make copy of unique_ptr. But if I change signature to:
void push(T && param) {...}
and the function call
queue.push( std::move(d1) );
I have this error again. So the question is - how should I implement push() that will move unique_ptr to the queue?
You should modify your class to handle move only type:
template <typename T>
class Queue
{
public:
void push(const T & param)
{
m_deque.push_front(param);
}
void push(T&& param)
{
m_deque.push_front(std::move(param));
}
private:
std::deque<T> m_deque;
};
With usage:
int main()
{
Queue< std::unique_ptr<DataBox> > queue;
std::unique_ptr<DataBox> d1(new DataBox(11));
queue.push(std::move(d1));
}
If you would like to make it universal, so that push can take either l or r value you could try something like this:
#include <iostream>
#include <memory>
#include <deque>
#include <utility>
class DataBox
{
public:
DataBox(int i) {}
};
template <typename T>
class Queue
{
public:
template <typename U>
void push(U &¶m)
{
m_deque.push_front(std::forward<T>(param));
}
private:
std::deque<T> m_deque;
};
int main()
{
Queue<std::unique_ptr<DataBox>> queue;
std::unique_ptr<DataBox> d1 = std::make_unique<DataBox>(11);
std::unique_ptr<DataBox> d2 = std::make_unique<DataBox>(22);
queue.push(d1);
queue.push(std::move(d2));
return 0;
}
https://ideone.com/ixzEmN
You have the following options.
Option 1: concise and usually fast
template <typename T>
class Queue {
public:
void push(T param) {
m_deque.push_front(std::move(param));
}
private:
std::deque<T> m_deque;
};
I would recommend this for most practical cases, since it's simple. However, if T isn't efficiently movable, it will do two copies instead of one.
Option 2: always fast
template <typename T>
class Queue {
public:
void push(const T& param) {
m_deque.push_front(param);
}
void push(T&& param) {
m_deque.push_front(std::move(param));
}
private:
std::deque<T> m_deque;
};
This is the most optimal solution, but it might be an overkill. If T is efficiently movable (and you should strive to make all types efficiently movable), then it does exactly the same amount of copies as option 1:
Queue<std::vector<int>> q;
std::vector<int> v;
q.push(v); // one copy, both with option 1 and option 2
q.push(std::move(v)); // no copies, both with option 1 and option 2
However:
Queue<NotEfficientlyMovableType> q;
NotEfficientlyMovableType x;
q.push(x); // one copy with option 2, two copies with option 1
q.push(std::move(x)); // same (so it doesn't really make sense)
Option 3: concise, always fast, but has caveats
template <typename T>
class Queue {
public:
template <typename U>
void push(U&& param)
{
m_deque.push_front(std::forward<T>(param));
}
private:
std::deque<T> m_deque;
};
With option 3 you only get to write one function and it always does the minimum number of copies (like option 2). However, it requires template argument deduction, which can break valid code. For example, this works with options 1 and 2, but not with option 3:
Queue<std::pair<int, int>> q;
q.push({1, 2});
Related
I have a class that contains several vectors of unrelated classes.
class Class0 {};
class Class1 {};
class Class2 {};
enum class RemoveFromVector : uint8_t { CLASS0 = 0, CLASS1 = 1, CLASS2 = 2 };
class C
{
public:
std::vector<Class0> c0s;
std::vector<Class1> c1s;
std::vector<Class2> c2s;
void RemoveFromVector(const RemoveFromVector RFV, const int Index)
{
// I'd like to replace this switch with something that's compile-time
switch ((uint8_t)RFV)
{
case 0: c0s.erase(c0s.begin() + Index); break;
case 1: c1s.erase(c1s.begin() + Index); break;
case 2: c2s.erase(c2s.begin() + Index); break;
default: break;
}
}
};
int main()
{
C c;
c.c0s.push_back(Class0());
c.c1s.push_back(Class1());
c.c1s.push_back(Class1());
c.c1s.push_back(Class1());
c.c2s.push_back(Class2());
// this should remove the last element from C.c1s
c.RemoveFromVector(RemoveFromVector::CLASS1, 2);
}
How would I write a function that removes an element from one of the vectors based on an enum (or int) at runtime without having to write a switch that's got a case for every single vector?
In other words, I'm looking for a way to deduce which vector to call erase() on statically at compile-time, which I then call at runtime. The correct term might be "static dispatch" though I'm not entirely certain.
You should keep the code as much as simple as possible. As per the currently shown code, it is simple and readable for every developer who works later on the codebase.
Secondly, the internal storage via std::vectors will make this task anyways at run-time. Because, most of the operations happen with the std::vector is run time overhead as they allocate the memory and manage it at run time. Therefore you can not do any compile-time work for std::vector::erase and whatsoever.
That being said, if you insist to avoid the switch statement, and bring the template complication, one is below, which still would have kind of template type mapping using if constexpr, and the vector erase happens at run-time.
#include <type_traits> // std::is_same_v
class C
{
template<typename ClassType>
auto& getVectorOf() /* noexcept */
{
if constexpr (std::is_same_v<ClassType, Class0>) return c0s;
else if constexpr (std::is_same_v<ClassType, Class1>) return c1s;
else if constexpr (std::is_same_v<ClassType, Class2>) return c2s;
}
public:
std::vector<Class0> c0s; // recommended to be private!
std::vector<Class1> c1s;
std::vector<Class2> c2s;
template<typename ClassType>
void RemoveFromVector(const std::size_t index) /* noexcept */
{
// some index check!
auto& vec = getVectorOf<ClassType>();
vec.erase(vec.begin() + index);
}
};
Call the function like
C c;
// fill the vectors
c.RemoveFromVector<Class0>(0);
c.RemoveFromVector<Class1>(2);
c.RemoveFromVector<Class2>(0);
(See a Demo Online)
Here's a slightly more generic solution:
template <typename... Types>
class MultiStack
{
public:
template <typename T>
/*const*/ T& Get() /*const*/
{
return GetStack<T>().back();
}
template <typename T>
void Push( const T& t )
{
GetStack<T>().push_back( t );
}
template <typename T>
void Pop()
{
GetStack<T>().pop_back();
}
template <size_t... Sizes>
void Reserve()
{
auto reserve = [&]( auto&... stacks ) { ( stacks.reserve( Sizes ), ... ); };
std::apply( reserve, Stacks );
}
private:
template <typename T>
std::vector<T>& GetStack()
{
return std::get<std::vector<T>>( Stacks );
}
std::tuple<std::vector<Types>...> Stacks;
};
Usage looks nice and simple:
using MyStack = MultiStack<Class0,Class1,Class2>;
MyStack stack;
stack.Push( ClassX() ); // automatically pushes any compatible object on the appropriate stack
stack.Pop<Class1>(); // pops/"erases" last object of the Class1 stack (vector)
You could extend this if you need to Pop more than one object at a time, or 'call other functions' on the vector. You could/should also make Get return const T& depending on your needs. (Get() const will need GetStack() const)
I have left in the fancy Reserve() just to show off. ;-) You will probably want to set certain but different initial sizes for your stacks.
I think std::variant and std::visit are the way you can go. You also can use a new helper function make_variant_array to make the code even shorter:
#include <cstdint>
#include <vector>
#include <variant>
#include <array>
#include <functional>
class Class0 {};
class Class1 {};
class Class2 {};
enum class RemoveFromVector
: uint8_t
{
Class0 = 0, Class1 = 1, Class2 = 2
};
template <class... Args>
auto make_variant_array(Args&... args)
{
using var_t = std::variant<std::reference_wrapper<Args>...>;
return std::array<var_t, sizeof...(Args)>{std::ref(args)...};
}
class C
{
public:
std::vector<Class0> c0s;
std::vector<Class1> c1s;
std::vector<Class2> c2s;
void RemoveFromVector(const RemoveFromVector RFV, const int Index)
{
static auto lookup = make_variant_array(c0s, c1s, c2s);
std::visit([Index](auto& vec) { vec.get().erase(vec.get().begin() + Index); }, lookup[(uint8_t)RFV]);
}
};
int main()
{
C c;
c.c0s.push_back(Class0());
c.c1s.push_back(Class1());
c.c1s.push_back(Class1());
c.c1s.push_back(Class1());
c.c2s.push_back(Class2());
// this should remove the last element from C.c1s
c.RemoveFromVector(RemoveFromVector::Class1, 2);
}
Inspired by this code, I am trying to implement a Reader/Writer vector that can safely call push_back() concurrently by threads.
Once this class is in place, I might then create method erase() by calling std::swap(), which swaps the target item and the last item and then erase the last item in the collection. In this way, I assume that the performance should be fair because deleting an item in the middle of collection does not invoke moving all items following the target item in the collection.
Unfortunately, the following code:
#include <vector>
#include <boost/thread/shared_mutex.hpp> //shared_mutex
#include <memory> //shared_ptr
#include <utility> //swap()
template <class T>
class readers_writer_vector
{
std::shared_ptr<boost::shared_mutex> pm;
std::vector<T> data;
public:
readers_writer_vector() :
pm(new std::shared_ptr<boost::shared_mutex>){}
void push_back(const T& item){
boost::unique_lock<boost::shared_mutex> lock(*pm); //wrong design
data.push_back(item);
}
};
int main()
{
readers_writer_vector<int> db;
db.push_back(1);
return 0;
}
yields the following compilation errors:
/usr/include/c++/4.9/bits/shared_ptr_base.h:871:39: error: cannot convert ‘std::shared_ptr<boost::shared_mutex>*’ to ‘boost::shared_mutex*’ in initialization
: _M_ptr(__p), _M_refcount(__p)
// g++ -std=c++11 -Iboost -lboost t.cpp
How do I fix it? Please!
EDIT:
The implementation task is far more complicated than I thought. It didn't take too long before I encountered the problem #Danh had warned. Now I get these errors:
t.cpp:28:8: note: ‘i::i(const i&)’ is implicitly deleted because the default definition would be ill-formed:
struct i {
^
t.cpp:28:8: error: use of deleted function ‘readers_writer_vector<T>::readers_writer_vector(const readers_writer_vector<T>&) [with T = z]’
t.cpp:13:2: note: declared here
readers_writer_vector(readers_writer_vector const&) = delete;
with this version:
template <class T>
class readers_writer_vector
{
booster::shared_mutex m;
std::vector<T> data;
public:
readers_writer_vector() = default;
readers_writer_vector(readers_writer_vector const&) = delete;
void push_back(const T& item){
booster::unique_lock<booster::shared_mutex> lock(m);
data.push_back(item);
}
typename std::vector<T>::reference back(){
return data.back();
}
};
struct z {
int zipcode;
std::string address;
};
struct i {
int id;
readers_writer_vector<z> zipcodes;
};
int main()
{
readers_writer_vector<i> db;
db.push_back(i());
auto &ii=db.back();
ii.id=1;
ii.zipcodes.push_back(z());
auto &zz=ii.zipcodes.back();
zz.zipcode=11;
zz.address="aa";
return 0;
}
In addition to fixing the existing errors, I will have to implement iterators for readers_writer_vector to make this class useful.
I am pondering whether or not I should continue...
Because pm is std::shared_ptr<boost::shared_mutex> not std::shared_ptr<boost::shared_mutex>*. You can use this:
readers_writer_vector() :
pm(std::make_shared<boost::shared_mutex>()){}
Anyway, why do you need pointer/smart pointer? This is better fit:
template <class T>
class readers_writer_vector
{
boost::shared_mutex pm;
std::vector<T> data;
public:
void push_back(const T& item){
boost::unique_lock<boost::shared_mutex> lock(pm);
data.push_back(item);
}
};
You're initialising pm with the wrong type; you effectively have
std::shared_ptr<> pm = new std::shared_ptr<>;
You can't assign a shared pointer from a pointer to shared pointer.
Replace the initialiser with
pm(new boost::shared_mutex)
or make the mutex a member directly, rather than using shared pointer.
Suppose I have this class :
class Component1;
class Component2;
// many different Components
class Component42;
class MyClass
{
public:
MyClass(void) {};
std::list<Component1> component1List;
std::list<Component2> component2List;
// one list by component
std::list<Component42> component42List;
};
I would like to create a function with the following signature:
template<class T> void addElement(T component);
It should do the following:
if component is of type Component1, add it to Component1List
if component is of type Component2, add it to Component2List, etc.
Is it possible? What's a good way to do this?
I can obtain the same behaviour with a function like :
template<class T> void addElement(int componentType, T component);
but I'd rather not have to specify the componentType like this : it's useless information and it open the door to possible errors (if componentType doesn't represent the type of component).
std::tuple to the rescue.
changelog:
use std::decay_t
added the variadic argument form
add_component() now returns a reference to this to allow call-chaining.
#include <iostream>
#include <list>
#include <utility>
#include <type_traits>
#include <tuple>
class Component1 {};
class Component2 {};
struct Component3 {
Component3() {}
};
// many different Components
template<class...ComponentTypes>
class MyClassImpl
{
template<class Component> using list_of = std::list<Component>;
public:
using all_lists_type =
std::tuple<
list_of<ComponentTypes> ...
>;
// add a single component
template<class Component>
MyClassImpl& add_component(Component&& c)
{
list_for<Component>().push_back(std::forward<Component>(c));
return *this;
}
// add any number of components
template<class...Components>
MyClassImpl& add_components(Components&&... c)
{
using expand = int[];
void(expand { 0, (void(add_component(std::forward<Components>(c))), 0)... });
return *this;
}
template<class Component>
auto& list_for()
{
using component_type = std::decay_t<Component>;
return std::get<list_of<component_type>>(_lists);
}
template<class Component>
const auto& list_for() const
{
using component_type = std::decay_t<Component>;
return std::get<list_of<component_type>>(_lists);
}
private:
all_lists_type _lists;
};
using MyClass = MyClassImpl<Component1, Component2, Component3>;
int main()
{
MyClass c;
c.add_component(Component1());
c.add_component(Component2());
const Component3 c3;
c.add_component(c3);
c.add_components(Component1(),
Component2(),
Component3()).add_components(Component3()).add_components(Component1(),
Component2());
std::cout << c.list_for<Component1>().size() << std::endl;
return 0;
}
The most straightforward variant is to simply not use templates but to overload the addElement() function:
void addElement(Component1 element)
{
this->element1List.push_back(element);
}
void addElement(Component2 element)
{
this->element2List.push_back(element);
}
// ... etc
However, this might get tedious if you have many of these (and you don't just have addElement(), I guess). Using a macro to generate the code for each type could still do the job with reasonable effort.
If you really want to use templates, you could use a template function and specialize the template function for each type. Still, this doesn't reduce the amount of code repetition when compared with the above approach. Also, you could still reduce it using macros to generate the code.
However, there's hope for doing this in a generic way. Firstly, let's create a type that holds the list:
template<typename T>
struct ComponentContainer
{
list<T> componentList;
};
Now, the derived class just inherits from this class and uses C++ type system to locate the correct container baseclass:
class MyClass:
ComponentContainer<Component1>,
ComponentContainer<Component2>,
ComponentContainer<Component3>
{
public:
template<typename T>
void addElement(T value)
{
ComponentContainer<T>& container = *this;
container.componentList.push_back(value);
}
}
Notes here:
This uses private inheritance, which is very similar to the containment you originally used.
Even though ComponentContainer is a baseclass, it doesn't have any virtual functions and not even a virtual destructor. Yes, this is dangerous and should be documented clearly. I wouldn't add a virtual destructor though, because of the overhead it has and because it shouldn't be needed.
You could drop the intermediate container altogether and derive from list<T>, too. I didn't because it will make all of list's memberfunctions available in class MyClass (even if not publicly), which might be confusing.
You can't put the addElement() function into the base class template to avoid the template in the derived class. The simple reason is that the different baseclasses are scanned in order for a addElement() function and only then overload resolution is performed. The compiler will only find the addElement() in the first baseclass therefore.
This is a plain C++98 solution, for C++11 I'd look at the type-based tuple lookup solutions suggested by Jens and Richard.
If there are not too many classes you could go with overloading. A template-based solution could be done with type-based lookup for tuples:
class MyClass {
public:
template<typename T> void addElement(T&& x) {
auto& l = std::get<std::list<T>>(lists);
l.insert( std::forward<T>(x) );
}
private:
std::tuple< std::list<Component1>, std::list<Component2> > lists;
};
If you don't know in advance the types you will need storing when instantiating the multi-container an option is to hide the types and using type_index to keep a map of lists:
struct Container {
struct Entry {
void *list;
std::function<void *(void*)> copier;
std::function<void(void *)> deleter;
};
std::map<std::type_index, Entry> entries;
template<typename T>
std::list<T>& list() {
Entry& e = entries[std::type_index(typeid(T))];
if (!e.list) {
e.list = new std::list<T>;
e.deleter = [](void *list){ delete ((std::list<T> *)list); };
e.copier = [](void *list){ return new std::list<T>(*((std::list<T> *)list)); };
}
return *((std::list<T> *)e.list);
}
~Container() {
for (auto& i : entries) i.second.deleter(i.second.list);
}
Container(const Container& other) {
// Not exception safe... se note
for (auto& i : other.entries) {
entries[i.first] = { i.second.copier(i.second.list),
i.second.copier,
i.second.deleter };
}
};
void swap(Container& other) { std::swap(entries, other.entries); }
Container& operator=(const Container& other) {
Container(other).swap(*this);
return *this;
};
Container() { }
};
that can be used as:
Container c;
c.list<int>().push_back(10);
c.list<int>().push_back(20);
c.list<double>().push_back(3.14);
NOTE: the copy constructor as written now is not exception safe because in case a copier throws (because of an out of memory or because a copy constructor of an element inside a list throws) the already allocated lists will not be deallocated.
void addElement(Component1 component) {
componentList1.insert(component);
}
void addElement(Component2 component) {
componentList2.insert(component);
}
suppose you have some code like this:
struct Manager
{
template <class T>
void doSomething(T const& t)
{
Worker<T> worker;
worker.work(t);
}
};
A "Manager" object is created once and called with a few diffent types "T", but each type T is called many times. This might be, in a simplified form, like
Manager manager;
const int N = 1000;
for (int i=0;i<N;i++)
{
manager.doSomething<int>(3);
manager.doSomething<char>('x');
manager.doSomething<float>(3.14);
}
Now profiling revealed that constructing a Worker<T> is a time-costly operation and it should be avoided to construct it N times (within doSomething<T>). For thread-safety reasons it is ok to have one Worker<int>, one Worker<char> and Worker<float> per "Manager", but not one Worker<int> for all Managers. So usually I would make "worker" a member variable. But how could I do this in the code above? (I do not know in advance which "T"s will be used).
I have found a solution using a std::map, but it is not fully typesafe and certainly not very elegant. Can you suggest a typesafe way without constructing Worker<T> more often than once per "T" without virtual methods?
(please note that Worker is not derived from any template-argument free base class).
Thanks for any solution!
You can use something like a std::map<std::type_info,shared_ptr<void> > like this:
#include <map>
#include <typeinfo>
#include <utility>
#include <functional>
#include <boost/shared_ptr.hpp>
using namespace std;
using namespace boost;
// exposition only:
template <typename T>
struct Worker {
void work( const T & ) {}
};
// wrapper around type_info (could use reference_wrapper,
// but the code would be similar) to make it usable as a map<> key:
struct TypeInfo {
const type_info & ti;
/*implicit*/ TypeInfo( const type_info & ti ) : ti( ti ) {}
};
// make it LessComparable (could spcialise std::less, too):
bool operator<( const TypeInfo & lhs, const TypeInfo & rhs ) {
return lhs.ti.before( rhs.ti );
}
struct Manager
{
map<TypeInfo,shared_ptr<void> > m_workers;
template <class T>
Worker<T> * findWorker()
{
const map<TypeInfo,shared_ptr<void> >::const_iterator
it = m_workers.find( typeid(T) );
if ( it == m_workers.end() ) {
const shared_ptr< Worker<T> > nworker( new Worker<T> );
m_workers[typeid(T)] = nworker;
return nworker.get();
} else {
return static_cast<Worker<T>*>( it->second.get() );
}
}
template <typename T>
void doSomething( const T & t ) {
findWorker<T>()->work( t );
}
};
int main() {
Manager m;
m.doSomething( 1 );
m.doSomething( 1. );
return 0;
}
This is typesafe because we use type_info as an index into the map. Also, the workers are properly deleted even though they're in shared_ptr<void>s because the deleter is copied from the original shared_ptr<Worker<T> >s, and that one calls the proper constructor. It also doesn't use virtual functions, although all type erasure (and this is one) uses something like virtual functions somewhere. Here, it's in shared_ptr.
Factoring the template-independent code from findWorker into a non-template function to reduce code bloat is left as an exercise for the reader :)
Thanks to all commenters who pointed out the mistake of using type_info as the key directly.
You can add std::vector of boost::variants or boost::anys as member of your class. And append to it any worker you want.
EDIT: The code bellow will explain how
struct Manager
{
std::vector<std::pair<std::type_info, boost::any> > workers;
template <class T>
void doSomething(T const& t)
{
int i = 0;
for(; i < workers.size(); ++i)
if(workers[i].first == typeid(T))
break;
if(i == workers.size())
workers.push_back(std::pair<std::type_info, boost::any>(typeid(T).name(), Worker<T>());
any_cast<T>(workers[i]).work(t);
}
};
I was already working on an answer similar to mmutz's by time he posted his. Here's a complete solution that compiles and runs under GCC 4.4.3. It uses RTTI and polymorphism to lazily construct Worker<T>s and store them in a map.
#include <iostream>
#include <typeinfo>
#include <map>
struct BaseWorker
{
virtual ~BaseWorker() {}
virtual void work(const void* x) = 0;
};
template <class T>
struct Worker : public BaseWorker
{
Worker()
{
/* Heavyweight constructor*/
std::cout << typeid(T).name() << " constructor\n";
}
void work(const void* x) {doWork(*static_cast<const T*>(x));}
void doWork(const T& x)
{std::cout << typeid(T).name() << "::doWork(" << x << ")\n";}
};
struct TypeofLessThan
{
bool operator()(const std::type_info* lhs, const std::type_info* rhs) const
{return lhs->before(*rhs);}
};
struct Manager
{
typedef std::map<const std::type_info*, BaseWorker*, TypeofLessThan> WorkerMap;
~Manager()
{
// Delete all BaseWorkers in workerMap_
WorkerMap::iterator it;
for (it = workerMap_.begin(); it != workerMap_.end(); ++it)
delete it->second;
}
template <class T>
void doSomething(T const& x)
{
WorkerMap::iterator it = workerMap_.find(&typeid(T));
if (it == workerMap_.end())
{
it = workerMap_.insert(
std::make_pair(&typeid(T), new Worker<T>) ).first;
}
Worker<T>* worker = static_cast<Worker<T>*>(it->second);
worker->work(&x);
}
WorkerMap workerMap_;
};
int main()
{
Manager manager;
const int N = 10;
for (int i=0;i<N;i++)
{
manager.doSomething<int>(3);
manager.doSomething<char>('x');
manager.doSomething<float>(3.14);
}
}
map<std::type_info, BaseWorker*> doesn't work because type_info is not copy-constructible. I had do use map<const std::type_info*, BaseWorker*>. I just need to check that typeid(T) is guaranteed to always return the same reference (I think it is).
It doesn't matter whether or not typeid(T) returns the same reference, because I always use type_info::before do to all comparisons.
something like this will work:
struct Base { };
template<class T> struct D : public Base { Manager<T> *ptr; };
...
struct Manager {
...
Base *ptr;
};
Suppose I have an autolocker class which looks something like this:
template <T>
class autolocker {
public:
autolocker(T *l) : lock(l) {
lock->lock();
}
~autolocker() {
lock->unlock();
}
private:
autolocker(const autolocker&);
autolocker& operator=(const autolocker&);
private:
T *lock;
};
Obviously the goal is to be able to use this autolocker with anything that has a lock/unlock method without resorting to virtual functions.
Currently, it's simple enough to use like this:
autolocker<some_lock_t> lock(&my_lock); // my_lock is of type "some_lock_t"
but it is illegal to do:
autolocker lock(&my_lock); // this would be ideal
Is there anyway to get template type deduction to play nice with this (keep in my autolocker is non-copyable). Or is it just easiest to just specify the type?
Yes you can use the scope-guard technique
struct autolocker_base {
autolocker_base() { }
protected:
// ensure users can't copy-as it
autolocker_base(autolocker_base const&)
{ }
autolocker_base &operator=(autolocker_base const&)
{ return *this; }
};
template <T>
class autolocker : public autolocker_base {
public:
autolocker(T *l) : lock(l) {
lock->lock();
}
autolocker(const autolocker& o)
:autolocker_base(o), lock(o.lock)
{ o.lock = 0; }
~autolocker() {
if(lock)
lock->unlock();
}
private:
autolocker& operator=(const autolocker&);
private:
mutable T *lock;
};
Then write a function creating the autolocker
template<typename T>
autolocker<T> makelocker(T *l) {
return autolocker<T>(l);
}
typedef autolocker_base const& autolocker_t;
You can then write it like this:
autolocker_t lock = makelocker(&my_lock);
Once the const reference goes out of scope, the destructor is called. It doesn't need to be virtual. At least GCC optimizes this quite well.
Sadly, this means you have to make your locker-object copyable since you need to return it from the maker function. But the old object won't try to unlock twice, because its pointer is set to 0 when it's copied, so it's safe.
Obviously you can't get away with autolocker being a template, because you want to use it as a type, and templates must be instantiated in order to obtain types.
But type-erasure might be used to do what you want. You turn the class template into a class and its constructor into a member template. But then you'd have to dynamically allocate an inner implementation object.
Better, store a pointer to a function that performs the unlock and let that function be an instance of a template chosen by the templatized constructor. Something along these lines:
// Comeau compiles this, but I haven't tested it.
class autolocker {
public:
template< typename T >
autolocker(T *l) : lock_(l), unlock_(&unlock<T>) { l->lock(); }
~autolocker() { unlock_(lock_); }
private:
autolocker(const autolocker&);
autolocker& operator=(const autolocker&);
private:
typedef void (*unlocker_func_)(void*);
void *lock_;
unlocker_func_ unlock_;
template <typename T>
static void unlock(void* lock) { ((T*)lock)->unlock(); }
};
I haven't actually tried this and the syntax might be wrong (I'm not sure how to take the address of a specific function template instance), but I think this should be doable in principle. Maybe someone comes along and fixes what I got wrong.
I like this a lot more than the scope guard, which, for some reason, I never really liked at all.
I think jwismar is correct and what you want is not possible with C++. However, a similar (not direct analogue) construct is possible with C++0x, using several new features (rvalues/moving and auto variable type):
#include <iostream>
template <typename T>
class autolocker_impl
{
public:
autolocker_impl(T *l) : lock(l) {
lock->lock();
}
autolocker_impl (autolocker_impl&& that)
: lock (that.lock)
{
that.lock = 0;
}
~autolocker_impl() {
if (lock)
lock->unlock();
}
private:
autolocker_impl(const autolocker_impl&);
autolocker_impl& operator=(const autolocker_impl&);
private:
T *lock;
};
template <typename T>
autolocker_impl <T>
autolocker (T* lock)
{
return autolocker_impl <T> (lock);
}
struct lock_type
{
void lock ()
{ std::cout << "locked\n"; }
void unlock ()
{ std::cout << "unlocked\n"; }
};
int
main ()
{
lock_type l;
auto x = autolocker (&l);
}
autolocker is a class template, not a class. Your "this would be ideal" is showing something that doesn't make sense in C++.