2016-08-02 91 views
8

我在我的应用程序中有一个Signal类,它为类提供了一个公开事件(与.NET相同)的选项。C++ 11 std ::转发指针

班级运作良好。

昨天我看到this SO question (and its answer)并且熟悉了std::forward

我决定尝试在我的代码中使用它,所以我改为每std::function<void(Args...)>std::function<void(Args&&...)>,并在提高功能(operator())我使用了我在上面的链接看到了同样的逻辑,所以现在该功能将Args&&...args和回调使用std::forward<Args>(args)...

这里是我的信号类的简化版本(含部分变更,使之成为很好的例子):

template<typename... Args> class Signal 
{ 
public: 
    int operator+=(const std::function<void(Args&&...)>& func) { 
     int token = getNewToken(); 
     m_subscribers.emplace(token, func); 
     return token; 
    } 

    void operator()(Args&&... args) { 
     for (auto it : m_subscribers) { 
      it.second(std::forward<Args>(args)...); 
     } 
    } 

private: 
    std::map<int, std::function<void(Args&&...)>> m_subscribers; 
}; 

int main() { 
    int* six = new int(6); 
    int seven = 7; 

    Signal<int*> e1; 
    e1 += [](int* x) { std::cout << *x; }; 

    Signal<int> e2; 
    e2 += [](int x) { std::cout << x; }; 

    e1(&seven); 
    e2(6); 

    e1(six); //Error C2664 'void Signal<int *>::operator()(int *&&)': 
       // cannot convert argument 1 from 'int *' to 'int *&&' 
    e1(std::move(six)); //This is a workaround   
    return 0; 
} 

我看到的问题是与类(或main在此示例中)尝试用普安特来举办活动rs和我不知道如何解决这个问题。

我的主要目标是让Signal类成为一个通用API,如果开发人员选择使用Signal<int*>我不希望他用std::move加注。

我在做什么错在这里?

回答

11

T&&只是一个通用参考,如果T是一个非cv限定的函数模板参数。在您的电话运营商:

void operator()(Args&&... args) { 

Args是不是函数的模板参数,它是类的模板参数。因此,对于Signal<int*>,此operator()取右值参考int*。由于six是一个左值,因此失败。

你想要的是给Signal提供正确的参考资格。像这样:

template<typename... Args> 
class Signal 
{ 
    using F = std::function<void(Args...)>; // NB: Just Args... 
public: 
    int operator+=(F func) { 
     int token = getNewToken(); 
     m_subscribers.emplace(token, std::move(func)); 
     return token; 
    } 

    void operator()(Args... args) {  // NB: just Args... 
     for (auto& it : m_subscribers) { // NB: auto& 
      it.second(args...); 
     } 
    } 

private: 
    std::map<int, F> m_subscribers; 
}; 

请注意,转发Args...无论如何都是有问题的。如果你有两个用户呢?一旦你转发了参数,一旦你不能再真正使用它们。

以上将使Signal<int*>做你所期望的。 operator()只需要一个int*,您可以将左值或右值传递给。

+2

@WernerErasmus是的。如果您有多个订户,则无法多次转发相同的参数。 – Barry

+0

感谢Barry,除了'使用F'部分,这是我已经使用的代码。所以基本上你的答案是“你不能也不应该在这种情况下使用std :: forward”? – ZivS

+0

我更关心如何以及如果我可以使用所有类型的std :: forward。我觉得我应该多读一些,因为你的答案的第一段对我来说有点核心...... – ZivS

1

巴里的答案是正确的,但可能不是那么清楚地解释。

&&只有在模板参数扣除发生时才作为转发(或“通用”)参考的特殊处理。但是这里存在不扣:

Signal<int*> e1; // `Args...` is explicitly `int*` 
... 
e1(six);   // `Args...` *has already been specified* 

当模板类实例化,它们基本上转化为正常类,只是碰巧被编译器写入。请参阅this answer以查看以C++代码写出的内容的示例。

在C++ 14,有没有引发的类模板参数推导没有辅助功能(不是构造)方式:

template <typename Args...> 
Signal<Args...> make_signal(Args&&...) { return Signal<Args...>; } 

....不过需要注意的那在你的情况下,这是毫无意义的:你不想推断你创建的参数的类型为Signal,你想要指定他们提前。

(请注意,在C++ 17,有template argument deduction of class templates支持。我认为,这意味着,这将是可能forward模板类的参数,但它不是立即清楚我在做什么的影响这样的事情将是)

你想要允许的是参数将被转发在通话时间。这实际上是相当简单:

template<typename... Args> class Signal 
{ 
public: 
    // .... skipping some code... 

    template <typename... CallArgs> 
    void operator()(CallArgs&&... args) { 
     callback(std::forward<CallArgs>(args)...); 
    } 
}; 

....但同样,你的情况这并不完全意义,因为在巴里的回答说。如果您有多个回调,您不需要转发参数,以防止移动并重新使用它们。

可以通过检查m_subscribers的大小和仅使用forward的代码(如果它的代码为1)并仅以其他方式传递参数来解决此问题。但是,这可能会导致混淆行为,因为调用回调的方式通常不应取决于您的对象的状态。所以,你也许可以写一个单独的类,SingletonSignal,回调即必须forward版参数调用(例如,如果回调想要一个不可复制对象,如unique_ptr所有权转让)。