2012-07-20 64 views
4

让我们考虑一个简单的类右值引用和构造函数参数

template< typename T > 
class Wrapper { 
public: 
    // Constructors? 
private: 
    T wrapped; 
}; 

它应该使用什么样的构造是有效的?


C++之前0X会有一个构造函数之一:

  1. const引用(T const&) - 如果类型是T “重”,
  2. 或值(T) - 如果类型T是“轻”。

确定类型T是“重”还是“轻”并不容易。

人们可以假设只有内建类型(int/floats/...)是“轻”。但这并不完全正确,因为我们自己的Wrapper<int>最有可能也应该被视为“轻”类型。

boost::call_traits这样的库提供了一些手段,通过允许类型创建者将类型标记为“轻”(通过提供适当的call_traits专业化)来克服此困难。否则它将被视为“重”。似乎可以接受。


但是,C++ 0x使情况变得更糟。因为现在你也有参考(T&&),它允许有效地采取(一些)“重”的对象。

因为这个现在你必须之中选择和:

  1. 只是const引用(T const&) - 如果类型T是“重”,不支持移动语义(因为无论有没有 - 像大荚 - 或没有写,你有没有影响),
  2. 两个const引用(T const&)和右值引用(T&&) - 如果类型T是“重”,不支持移动语义,
  3. 只值(T) - 如果T类型是“轻量级”或者是“重量级”但支持移动语义(即使进行了复制,也不会打扰使用,否则我们不得不自己复制T const& ...)。

还不容易分辨哪些类型是“重”,哪些类型是“轻”(如以前)。但是现在你也不知道类型T是否支持移动语义(或者你是否?)。


一旦你换一个以上的值,因为可能的构造函数重载数量呈指数级增长,这变得更加恼人。

有没有解决这个问题的方法?

我虽然关于转发(完美转发)参数的一些模板构造函数,但我不确定是否会按需要工作。而且它还允许提供将被转发给T构造函数的不同类型的值。这可能被认为是一个功能,但不一定。

回答

7

相反,C++ 11使它更容易由于普遍引用:

template <typename T> struct Wrapper 
{ 
    T value; 

    template <typename U> Wrapper(U && u) 
    : value(std::forward<U>(u)) 
    { } 
}; 

作为一个额外的很好的接触,你要补充一点,只存在违约第二个参数时T是constructible U,以免让你的类本身看起来可以从不匹配的类型中构造出来。并使其可变参数,也:

template <typename ...Args> 
Wrapper(Args &&... args, 
     typename std::enable_if<std::is_constructible<T, Args...>::value, int>::type = 0) 
: value(std::forward<Args>(args)...) 
{ } 

确保#include <utility>forward#include <type_traits>的特质。

+0

您也可以使用具有相同完美转发的可变参数构造函数来构造'value'。您只需编写一个通用构造函数:'template Wrapper(U && ... us):value(std :: forward (us)...){} – 2012-07-20 11:29:43

+0

@AlexandreC:是是的,已经在那:-) – 2012-07-20 11:30:02

+0

只有一个问题:'std :: enable_if'在这里并不是必须的(它不会改善错误报告)。这里有什么用处? – 2012-07-20 11:32:36

3

如果您打算复制您的T,最好是按值传递参数并让编译器找出复制。无论你做什么,无论如何至少会有一个副本。

template< typename T > 
class Wrapper { 
public: 
  Wrapper(T value) : wrapped(std::move(value)) 
    { } 
private: 
  T wrapped; 
}; 

请参阅Dave Abrahams的Want speed? Pass by value

+0

请注意,如果预计'Wrapper '是'Wrapper'的有效实例化,那么您想要使用'std :: forward (value)'。 – 2012-07-21 02:25:14

+0

在我看来(从文章中),如果且仅当T是可移动的,这才会成立。如果不是这样,这将不是最理想的。在一个你永远不知道的模板中。 – 2012-07-21 11:58:32

+0

@Adam - 否。如果该类型只有一个拷贝构造函数,则副本可以被省略。您将需要*一个*副本来存储该值,但编译器可以优化其他不必要的复制。 – 2012-07-21 12:19:55