2016-12-02 60 views
10

分配通用构造函数成员变量时请考虑以下的类foo1foo2的std ::移动或std ::向前C++

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

是它总是的foo1构造代表的正确方法的情况下初始化成员变量T?即通过使用std::move

由于需要转发到foo1的构造函数,构造函数foo2是否代表初始化成员变量foo1<T>的正确方法?即通过使用std::forward

更新

下面的示例使用std::movefoo1为失败:

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

因为我想吨至既引用类型和值类型这是一个问题。

+2

我会让知道背后原因的人发布一个完整的答案,但如果你想快速的是/否,我总是被告知你的'foo2'例子是正确的。 –

+1

这些不是“通用”(转发)引用 - 如果您需要转发引用,则需要模板化构造函数。 – Holt

+0

这里没有任务。 –

回答

10

这些示例都不使用通用引用(转发引用,因为它们现在称为)。

转发引用仅在类型推演存在的情况下形成,但foo1foo2的构造函数中的T&&未被推导,因此它只是右值引用。

由于二者都是右值引用,所以两者都应该使用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) } 
    { 
    } 
}; 

你不应该在foo1在这种情况下使用std::move,作为客户端代码可以通过左值和有对象无效默默地:

std::vector<int> v {0,1,2}; 
foo1<std::vector<int>> foo = v; 
std::cout << v[2]; //yay, undefined behaviour 

一个更简单的方法是通过值取和无条件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) } 
    { 
    } 
}; 

完美转发版本:

  • 传递左值 - >一个副本
  • 传递右值 - >一招

对于传值和移动版本:

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

考虑这个代码需要怎样高性能是,它会需要多少改变和维护,并选择基础上的选项。

+0

_“在这种情况下,您不应该在foo1中使用std :: move,如果构造函数传递了非const的左值,它将无法编译。”_为什么? –

+0

@LightnessRacesinOrbit哎呦,修正了,谢谢。 – TartanLlama

+0

感谢您的回答,这是否意味着我会在这种情况下使用'std :: move':'template auto make_foo1(T && t){return foo1 {std :: move(t)}; }'因为没有类型扣除? – keith

0

这取决于你如何演绎T。例如:

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

在这种情况下,在foo1<T>T是转发引用,您的代码将无法编译。

std::vector<int> bob{1,2,3}; 
auto foo = make_foo1(bob); 

上面的代码从bob默默地移入std::vector<int>&构造内foo1<std::vector<int>&>

这样做与foo2一样可行。你会得到一个foo2<std::vector<int>&>,它将持有对bob的参考。

当你写一个模板时,你必须考虑T这个类型是什么意思。如果您的代码不支持将其作为参考,请考虑使用static_assert或SFINAE来阻止该情况。

template <typename T> 
struct foo1 { 
    static_assert(!std::is_reference<T>{}); 
    T t_; 

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

此代码生成一个合理的错误消息。

你可能会认为现有的错误信息是好的,但它只是好的,因为我们搬进了T

template <typename T> 
struct foo1 { 
    static_assert(!std::is_reference<T>{}); 

    foo1(T&& t) 
    { 
    auto internal_t = std::move(t); 
    } 
}; 

这里只static_assert确保我们T&&是实际右值。


但足够与这个理论列表的问题。你有一个具体的。

到底这可能是想你想:

template <class T> // typename is too many letters 
struct foo1 { 
    static_assert(!std::is_reference<T>{}); 
    T t_; 

    template<class U, 
    class dU=std::decay_t<U>, // or remove ref and cv 
    // SFINAE guard required for all reasonable 1-argument forwarding 
    // reference constructors: 
    std::enable_if_t< 
     !std::is_same<dU, foo1>{} && // does not apply to `foo1` itself 
     std::is_convertible<U, T> // fail early, instead of in body 
    ,int> = 0 
    > 
    foo1(U&& u): 
    t_(std::forward<U>(u)) 
    {} 
    // explicitly default special member functions: 
    foo1()=default; 
    foo1(foo1 const&)=default; 
    foo1(foo1 &&)=default; 
    foo1& operator=(foo1 const&)=default; 
    foo1& operator=(foo1 &&)=default; 
}; 

或者,这只是在99/100情况一样好简单的情况:

template <class T> 
struct foo1 { 
    static_assert(!std::is_reference<T>{}); 
    T t_; 

    foo1(T t) : 
    t_{ std::move(t) } 
    {} 
    // default special member functions, just because I never ever 
    // want to have to memorize the rules that makes them not exist 
    // or exist based on what other code I have written: 
    foo1()=default; 
    foo1(foo1 const&)=default; 
    foo1(foo1 &&)=default; 
    foo1& operator=(foo1 const&)=default; 
    foo1& operator=(foo1 &&)=default; 
}; 

作为一般规则,这更简单的技术比完美的转发技术的结果只有更多的代价和复杂性。它允许{}初始化T t参数给你的构造函数,这很好。