让我们考虑一个简单的类
template< typename T >
class Wrapper {
public:
// Constructors?
private:
T wrapped;
};
它应该使用什么构造函数才能有效?
在C ++ 0x之前,会有一个构造函数采用:
T const&
) - 如果类型T
是“重”,T
) - 如果类型T
是“亮”。确定类型T
是“重”还是“轻”是不容易的。
可以假设只有内置类型(ints / floats / ...)是“轻量级”。但这并不完全正确,因为我们自己的Wrapper<int>
最有可能被视为“轻型”类型。
boost::call_traits
这样的库通过允许类型创建者将类型标记为“轻”(通过提供适当的call_traits
特化)来提供一些克服此困难的方法。否则它将被视为“沉重”。似乎可以接受。
但是C ++ 0x会让事情变得更糟。因为现在你还有左值引用(T&&
),它可以有效地获取(某些)“重”对象。
因此,你必须选择:
T const&
) - 如果类型T
是“重”并且不支持移动语义(因为没有任何一个 - 就像大POD一样 - 或者没有写入并且你有对此没有影响),T const&
)和右值引用(T&&
) - 如果类型T
“重”且支持移动语义,T
) - 如果类型T
是“轻”或者它是“重”但支持移动语义(即使复制,它也不会使用,否则我们无论如何都要从T const&
复制......)。仍然不容易判断哪些类型“重”且哪些类型“轻”(如前所述)。但是现在你也无法判断类型T
是否支持移动语义(或者是你?)。
一旦你包含多个值,这就变得更加烦人,因为可能的构造函数重载次数呈指数级增长。
这个问题有解决办法吗?
我虽然有一些用于转发(完美转发)参数的模板构造函数,但我不确定它是否会按预期工作。并且它还允许提供将被转发到T
构造函数的不同类型的值。这可能被视为一项功能,但并非必须。
答案 0 :(得分:7)
相反,由于通用引用,C ++ 11使更容易:
template <typename T> struct Wrapper
{
T value;
template <typename U> Wrapper(U && u)
: value(std::forward<U>(u))
{ }
};
作为一个额外的好处,你应该添加一个默认的第二个参数,该参数仅在T
可以从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>
的特征为{{1}}。
答案 1 :(得分: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。