c ++ 11最佳参数传递

时间:2012-05-06 16:28:36

标签: c++ c++11 parameter-passing rvalue-reference

考虑这些课程:

#include <iostream>
#include <string>

class A
{
    std::string test;
public:
    A (std::string t) : test(std::move(t)) {}
    A (const A & other) { *this = other; }
    A (A && other) { *this = std::move(other); }

    A & operator = (const A & other)
    {
        std::cerr<<"copying A"<<std::endl;
        test = other.test;
        return *this;
    }

    A & operator = (A && other)
    {
        std::cerr<<"move A"<<std::endl;
        test = other.test;
        return *this;
    }
};

class B
{
    A a;
public:   
    B (A && a) : a(std::move(a)) {}
    B (A const & a) : a(a) {}
};

创建B时,我始终拥有A的最佳前向路径,一次为rvalues,一次为左值。

是否可以使用一个构造函数实现相同的结果?在这种情况下,这不是一个大问题,但是多个参数呢?我需要参数列表中每个可能出现的左值和右值的组合。

这不仅限于构造函数,也适用于函数参数(例如setter)。

注意:此问题严格来自 class B ; class A仅存在于可视化复制/移动调用的执行方式。

3 个答案:

答案 0 :(得分:9)

“按价值”方法是一种选择。它不像你拥有的那样最优,但只需要一次重载:

class B
{
    A a;
public:   
    B (A _a) : a(move(_a)) {}
};

对于左值和左值,成本是1个额外的移动构造,但这对于prvalues(1个移动)仍然是最佳的。 “xvalue”是使用std :: move。

转换为右值的左值

您还可以尝试“完美转发”解决方案:

class B
{
    A a;
public:   
    template <class T,
              class = typename std::enable_if
              <
                 std::is_constructible<A, T>::value
              >::type>
    B (T&& _a) : a(std::forward<T>(_a)) {}
};

这将使您回到最佳复制/移动结构数。但是你应该约束模板构造函数,使它不是过于通用。您可能更喜欢使用is_convertible而不是is_constructible,就像我上面所做的那样。这也是一个构造函数解决方案,但是当您添加参数时,您的约束变得越来越复杂。

注意:上面需要约束的原因是因为没有B的客户在查询std::is_constructible<B, their_type>::value时会得到错误的答案。如果没有B的适当约束,它将错误地回答为真。

我想说这些解决方案都不会比其他解决方案更好。这里有工程权衡。

答案 1 :(得分:2)

B的构造函数使用推导出的参数类型:

template <typename T> explicit B(T && x) : a(std::forward<T>(x) { }

这适用于A对象可构造的任何参数。

如果A有多个构造函数具有不同数量的参数,您可以通过在任何地方添加...来使整个事物变为可变的。

正如@Howard所说,你应该添加一个约束,这样这个类看起来似乎不是来自它实际上不是的参数。

答案 2 :(得分:1)

如果样本中的stringstd::string,则根本不关心:默认提供的副本和移动会在成员中调用各自的副本。 std::string已复制并移动两个实现,以便移动临时值,复制变量。

无需定义特定副本并移动ctor和assign。 你可以离开构造函数

A::A(string s) :test(std::move(s)) {}

通常,复制和移动的直接实现可以是以下

class A
{
public:
    A() :p() {}

    A(const A& a) :p(new data(*a.p)) {} //copy
    A(A&& a) :p(a.p) { a.p=0; }         //move

    A& operator=(A a) //note: pass by value
    { clear(); swap(a); return *this; }
    ~A() { clear(); }

    void swap(A& a) { std::swap(p,a.p); }
    void clear() { delete p; p=0; }

private:

    data* p;
};

operator=采用内部移动的值。如果它来自一个临时移动,如果它来自一个变量被复制。 复制和移动之间的区别需要不同的构造函数,但是,如果我们将A派生为

class B: public A
{
...
};

不需要覆盖任何内容,因为B的默认copy-ctor调用A的副本,B的默认移动调用A的移动,B的所有默认赋值运算符调用唯一定义的A对于A(根据转发的内容移动或复制)。