右值引用和构造函数参数

时间:2012-07-20 11:15:05

标签: c++ constructor c++11 rvalue-reference

让我们考虑一个简单的类

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

它应该使用什么构造函数才能有效?


在C ++ 0x之前,会有一个构造函数采用:

  1. const引用(T const&) - 如果类型T是“重”,
  2. 或值(T) - 如果类型T是“亮”。
  3. 确定类型T是“重”还是“轻”是不容易的。

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

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


    但是C ++ 0x会让事情变得更糟。因为现在你还有左值引用(T&&),它可以有效地获取(某些)“重”对象。

    因此,你必须选择:

    1. 只是const引用(T const&) - 如果类型T是“重”并且不支持移动语义(因为没有任何一个 - 就像大POD一样 - 或者没有写入并且你有对此没有影响),
    2. const引用(T const&)和右值引用(T&&) - 如果类型T“重”且支持移动语义,
    3. 只是值(T) - 如果类型T是“轻”或者它是“重”但支持移动语义(即使复制,它也不会使用,否则我们无论如何都要从T const&复制......)。
    4. 仍然不容易判断哪些类型“重”且哪些类型“轻”(如前所述)。但是现在你也无法判断类型T是否支持移动语义(或者是你?)。


      一旦你包含多个值,这就变得更加烦人,因为可能的构造函数重载次数呈指数级增长。

      这个问题有解决办法吗?

      我虽然有一些用于转发(完美转发)参数的模板构造函数,但我不确定它是否会按预期工作。并且它还允许提供将被转发到T构造函数的不同类型的值。这可能被视为一项功能,但并非必须。

2 个答案:

答案 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