【问题标题】:Template type deduction with a non-copyable class具有不可复制类的模板类型推导
【发布时间】:2010-05-15 22:46:50
【问题描述】:

假设我有一个看起来像这样的 autolocker 类:

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;
};

显然,目标是能够将此自动锁定器与任何具有锁定/解锁方法的东西一起使用,而无需借助虚拟功能。

目前,这样使用很简单:

autolocker<some_lock_t> lock(&my_lock); // my_lock is of type "some_lock_t"

但这样做是违法的:

autolocker lock(&my_lock); // this would be ideal

有没有办法让模板类型推导可以很好地使用这个(保存在我的自动锁柜中是不可复制的)。还是只指定类型最简单?

【问题讨论】:

  • 我很好奇:boost::mutex::scoped_lock 有什么不合适或不受欢迎的原因吗?
  • @greyfade:它用于我的玩具操作系统内核。所以我从头开始明智地使用图书馆:-)。
  • 那么公平。祝你好运。 :)

标签: c++ templates


【解决方案1】:

是的,您可以使用范围保护技术

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;
};

然后编写一个创建自动锁的函数

template<typename T>
autolocker<T> makelocker(T *l) {
  return autolocker<T>(l);
}

typedef autolocker_base const& autolocker_t;

你可以这样写:

autolocker_t lock = makelocker(&my_lock);

一旦 const 引用超出范围,就会调用析构函数。它不需要是虚拟的。至少 GCC optimizes this quite well

遗憾的是,这意味着您必须使您的储物柜对象可复制,因为您需要从 maker 函数中返回它。但是旧的对象不会尝试解锁两次,因为它的指针在被复制时被设置为0,所以它是安全的。

【讨论】:

  • +1。 boost::weak_ptr&lt;&gt; 使用了类似的模式,并且非常有效。
  • 现在你必须写makelocker而不是重复输入。当然,您不必以这种方式命名类型两次,但这仍然很麻烦。我认为这应该可以存储指向解锁静态成员函数实例的指针。这样你就可以写autolocker lock(&amp;mutex); 并完成。请参阅我的答案以获得草图。
  • 嗯,重新考虑后,我认为“autolocker_base”不需要公开复制。只有“autolocker”需要。相应地进行了调整。
【解决方案2】:

很明显,autolocker 是模板,因为您想将其用作类型,并且模板必须实例化才能获得类型。

但是类型擦除可以用来做你想做的事。你把类模板变成一个类,把它的构造函数变成一个成员模板。但是你必须动态分配一个内部实现对象。
更好的是,存储一个指向执行解锁的函数的指针,并让该函数成为模板化构造函数选择的模板的实例。类似这样的事情:

// 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(); }
};

我实际上并没有尝试过这个,语法可能是错误的(我不确定如何获取特定函数模板实例的地址),但我认为这在原则上应该是可行的。也许有人会出现并修复我做错的地方。

我更喜欢这个范围保护,出于某种原因,我从来没有真正喜欢过它。

【讨论】:

  • 哇,这是一个很好的方法 :) 我也比我的范围保护更喜欢它,因为它避免了复制构造函数/可变的疯狂:) 我想知道编译器的优化器是如何处理它?会很有趣:)
  • @Johannes:请记住,我什至还没有编译过这个。你认为它会起作用吗?无论如何,我认为编译器通常不擅长内联通过指针调用的函数。但我想这并不重要,因为在锁上调用unlock() 很可能会主导运行时。或者你在考虑什么优化?
  • 该死,Comeau 的在线编译器不喜欢它。无论如何,如果以这种方式获取静态成员函数模板的地址不起作用(这里是星期六晚上接近凌晨 2 点,我不打算解决这个问题),将其放入本地类模板的静态成员函数中可能会成功。
  • @sbi 是的,我认为这种技术会起作用。我记得 GCC 过去在获取特定函数模板专业化的地址时遇到问题,但公平地说,C++03 时代的标准并没有说明它会起作用。 C++0x 添加了使其工作的规则。 IIRC,这些规则是由我知道的当前 C++03 编译器追溯实施的(GCC、clang 和 comeau)。见open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#115。如果没有该规则,即使您要转换为 unlocker_func_,也永远不会产生指针,因为无法推断出 T
  • @sbi,在更改错别字之后,它在 Comeau 上对我来说编译得很好:)
【解决方案3】:

我认为 jwismar 是正确的,而 C++ 无法实现您想要的。然而,类似的(非直接模拟)构造可以使用 C++0x,使用几个新特性(右值/移动和自动变量类型):

#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);
}

【讨论】:

  • 哦,我现在明白我的布尔标志不是必需的了。我可以将指针用作“不调用解锁”标志,就像您在代码中所做的那样。 +1 为 C++0x 方式。
【解决方案4】:

autolocker 是类模板,而不是类。您的“这将是理想的”显示了在 C++ 中没有意义的东西。

【讨论】:

  • 如果我在近 20 年的 C++ 编程中学到了一件事,那就是“没有办法做到这一点”通常是一种暂时的状态。当然,autolocker 是一个模板,所以它不起作用,但是三个人想出了三种不同的技术来规避这个障碍。这是一种典型的情况,这就是为什么人们喜欢由同龄人来解决这样的问题:有人可能会把头扭到足够远,从一个没有人看过的角度来看问题,然后想出一种方法来解决问题。 :)
猜你喜欢
  • 2017-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-22
相关资源
最近更新 更多