C++11 std::forward 一个指针

C++11 std::forward a pointer

我的应用程序中有一个 Signal class,它为 classes 提供了一个公开事件的选项(与在 .NET 中一样)。

class 有效,一切正常。

昨天看了,熟悉了std::forward

我决定尝试在我的代码中使用它,所以我将每个 std::function<void(Args...)> 更改为 std::function<void(Args&&...)> 并且在 raise 函数(operator())中我使用了我看到的相同逻辑在上面 link 所以现在函数采用 Args&&...args 并且回调使用 std::forward<Args>(args)...

这是我的 Signal class 的简化版本(进行了一些更改以使其成为一个很好的示例):

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

我看到的问题是 classes(或本例中的 main)试图用指针引发事件,我不确定如何解决这个问题。

我的主要目标是让信号 class 通用 API 如果开发人员选择使用 Signal<int*> 我不想 him\her 提高std::move.

我做错了什么?

如果 T 是非 cv 限定的函数模板参数,则

T&& 只是一个通用引用。在您的电话接线员中:

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

Args 不是函数的模板参数,它是 class 的模板参数。因此,对于 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... 无论如何都是有问题的。如果您有两个订阅者怎么办?一旦你转发了 args 一次,你就不能真正第二次使用它们。

以上内容将使 Signal<int*> 达到您的期望。 operator() 将只接受一个 int*,您可以将左值或右值传递给它。

Barry 的回答是正确的,但可能解释得不够清楚。

&& 仅作为转发(或"universal")参考在发生模板参数推导时 给予特殊处理。但是这里没有扣除:

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

当模板 classes 被实例化时,它们本质上被转换为正常的 classes,只是恰好由编译器编写。请参阅 this answer 以了解用 C++ 代码编写时可能会是什么样子的示例。

在 C++14 中,没有 方法来触发 class 模板的模板参数推导而无需辅助 函数(不是构造函数):

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

...但请注意,在您的情况下,这没有任何意义:您不想在创建 [=14= 时 推断 参数的类型],你想提前指定它们。

(请注意,在 C++17 中, 支持 template argument deduction of class templates。我认为这意味着可以 forward template-class 参数,虽然我不是很清楚这样做的含义是什么。)

您想要允许的是在调用时转发参数。这实际上相当简单:

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

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

.....但是,再一次,在你的情况下,这不太有意义,正如 Barry 的回答中所指出的那样。如果您有多个回调,您不想转发参数,以防止移动和重新使用它们。

可以通过检查 m_subscribers 的大小来解决这个问题,如果它是 1 则只使用 forwarding 代码,否则按原样传递参数。但是,这可能会导致令人困惑的行为,因为调用回调的方式通常不应取决于 Signal 对象的状态。因此,您也许可以编写一个单独的 class、SingletonSignal,用于 必须 使用 forwarded 参数调用的回调(例如,如果回调需要转让不可复制对象(例如 unique_ptr)的所有权