在C++中将通用构造函数赋给成员变量时,std :: move或std :: forward

kei*_*ith 10 c++ move-semantics perfect-forwarding c++11 c++14

考虑以下类foo1foo2

template <typename T>
struct foo1
{
    T t_;

    foo1(T&& t) :
        t_{ std::move(t) }
    {
    }
};

template <typename T>
struct foo2
{
    foo1<T> t_;

    foo2(T&& t) :
        t_{ std::forward<T>(t) }
    {
    }
};
Run Code Online (Sandbox Code Playgroud)

始终是构造函数foo1表示初始化成员变量的正确方法T吗?即通过使用std::move.

总是这样的构造函数foo2表示foo1<T>由于需要转发到foo1的构造函数而初始化成员变量的正确方法吗?即通过使用std::forward.

更新

以下示例无法foo1使用std::move:

template <typename T>
foo1<T> make_foo1(T&& t)
{
    return{ std::forward<T>(t) };
}

struct bah {};

int main()
{
    bah b;

    make_foo1(b); // compiler error as std::move cannot be used on reference

    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

这是一个问题,因为我希望T既是引用类型又是值类型.

Tar*_*ama 11

这些示例都没有使用通用引用(转发引用,因为它们现在被称为).

转发引用仅在类型推导的情况下形成,但T&&在构造函数中foo1并且foo2不推导,因此它只是一个右值引用.

由于两者都是右值引用,因此您应该同时使用std::move它们.

如果要使用转发引用,则应使构造函数具有推导出的模板参数:

template <typename T>
struct foo1
{
    T t_;

    template <typename U>
    foo1(U&& u) :
        t_{ std::forward<U>(u) }
    {
    }
};

template <typename T>
struct foo2
{
    foo1<T> t_;

    template <typename U>
    foo2(U&& u) :
        t_{ std::forward<U>(u) }
    {
    }
};
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您不应该使用std::movein foo1,因为客户端代码可以传递左值并使对象无声地失效:

std::vector<int> v {0,1,2};
foo1<std::vector<int>> foo = v;
std::cout << v[2]; //yay, undefined behaviour
Run Code Online (Sandbox Code Playgroud)

一种更简单的方法是按值并无条件地std::move进入存储:

template <typename T>
struct foo1
{
    T t_;

    foo1(T t) :
        t_{ std::move(t) }
    {
    }
};

template <typename T>
struct foo2
{
    foo1<T> t_;

    foo2(T t) :
        t_{ std::move(t) }
    {
    }
};
Run Code Online (Sandbox Code Playgroud)

对于完美的转发版本:

  • 通过左值 - >一份
  • 传递右值 - >一举一动

对于传递值和移动版本:

  • 通过左值 - >一个副本,一个移动
  • 传递右值 - >两个动作

考虑这段代码需要具备的性能以及需要更改和维护的程度,并根据需要选择一个选项.