开发者

Template type deduction with a non-copyable class

开发者 https://www.devze.com 2022-12-31 11:28 出处:网络
Suppose I have an autolocker class which looks something like this: template <T> class autolocker {

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 au开发者_开发技巧tolocker 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++.

0

精彩评论

暂无评论...
验证码 换一张
取 消