【发布时间】:2013-01-06 12:59:01
【问题描述】:
我为通用信号/插槽系统编写了以下实现:
template< typename... Args >
class Signal : NonCopyable
{
public:
typedef std::function< void (Args...) > Delegate;
void connect(const Delegate& delegate);
void operator ()(Args&&... args) const;
private:
std::list< Delegate > _delegates;
};
template< typename... Args >
void Signal< Args... >::connect(const Delegate& delegate)
{
_delegates.push_front(delegate);
}
template< typename... Args >
void Signal< Args... >::operator ()(Args&&... args) const
{
for (const Delegate& delegate : _delegates)
delegate(std::forward< Args >(args)...);
}
之后,我使用以下简单案例测试了我的课程:
Signal< int > signal;
// Case 1
signal(0);
//Case 2
int i(0);
signal(i);
案例 1 编译没有问题。另一方面,情况 2 在 GCC 4.7.2 下会产生以下错误:
/home/pmjobin/Workspace/main.cpp:1196:10: error: cannot bind ‘int’ lvalue to ‘int&&’
/home/pmjobin/Workspace/main.cpp:82:6: error: initializing argument 1 of ‘void Signal<Args>::operator()(Args&& ...) const [with Args = {int}]’
我知道这个问题与完美转发有关(以及我对后者的误解)。但是,我从 std::make_shared() 和 std::make_tuple() 实现中得到了启发,我看不到将可变参数转发给代表的方式有什么不同。唯一显着的区别是 make_shared() 和 make_tuple() 都是函数模板,而不是像上面的 Signal 实现这样的类模板。
- 编辑-
针对各种 cmets,这里有一个新版本的 Signal 类实现,它不会遇到上述问题。此外,现在可以使用连接函数返回的不透明令牌断开委托。结果可能不像其他实现(例如 boost::signal)那样灵活和强大,但至少它具有简单和轻量级的优点。
template< typename Signature >
class Signal : NonCopyable
{
public:
typedef std::function< Signature > Delegate;
class DisconnectionToken
{
DisconnectionToken(typename std::list< Delegate >::iterator it)
: _it(it)
{}
typename std::list< Delegate >::iterator _it;
friend class Signal;
};
DisconnectionToken connect(const Delegate& delegate);
void disconnect(DisconnectionToken& token);
template< typename... Args >
void operator ()(Args&&... args) const;
private:
std::list< Delegate > _delegates;
};
template< typename Signature >
typename Signal< Signature >::DisconnectionToken Signal< Signature >::connect(const Delegate& delegate)
{
_delegates.push_front(delegate);
return DisconnectionToken(_delegates.begin());
}
template< typename Signature >
void Signal< Signature >::disconnect(DisconnectionToken& token)
{
if (token._it != _delegates.end())
{
_delegates.erase(token._it);
token._it = _delegates.end();
}
}
template< typename Signature >
template< typename... Args >
void Signal< Signature >::operator ()(Args&&... args) const
{
for (const Delegate& delegate : _delegates)
delegate(std::forward< Args >(args)...);
}
【问题讨论】:
-
Args&&... args基本上是int && args。 -
@Nawaz 是的,我错过了他在上面明确指定的内容。别管我刚才说的。是的,完美转发主要只在你让模板类型被推断出来时才有效。
-
OP 需要(或可以)做的是为
operator()提供一个单独的Args...。 -
@SethCarnegie:即使这样设计也有缺陷。完美的转发和
std::function的列表不会很顺利。 -
@SethCarnegie:完美转发是重载函数的解决方案,它接受参数为
X&&和X const&(例如)。
标签: c++ c++11 variadic-templates perfect-forwarding