2012-05-06 61 views
7

考虑这些类:C++ 11的最佳参数传递

#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,为右值的左值一招或一个副本。

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

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

注意:这个问题是严格约class B; class A仅用于可视化复制/移动调用的执行方式。

+0

你应该阅读这些:http://stackoverflow.com/questions/8472208/under-what-c​​onditions-should-i-be-thinking-about-implementing-a-move-constructor和http:// stackoverflow。 com/questions/4782757/rule-of-three-become-rule-of-five-with-c11 –

+0

@JamesCuster:我只是想测试各个构造函数/操作符被调用的次数。 – fscan

回答

9

“按价值”方法是一种选择。这是不是你有什么是最佳的,但只需要一个过载:

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

的成本是1个额外的举动建设两个左值和xvalues,但是这仍然是最佳的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没有适当的限制,它将错误地回答真实。

我会说,这些解决方案总是比其他解决方案更好。这里有工程折衷。

+0

关于按价值:在我的测试中,我还有一个额外的举动(vs2010)。 – fscan

+0

有趣。感谢那些信息。我无法访问vs2010。 –

+0

好吧,这很好,但如果应用于每个构造函数/设置器,会使代码不可读。有没有我不想从构造函数/设置器这种行为的情况。如果没有,编译器不能默认这样做,如果我声明的价值? (如果左值不再被使用,就像在这个构造函数中一样自动转换为xrvalue) – fscan

2

使用用于构造一个推导出参数类型B

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

这将用于从其中A对象可以作图任何参数工作。

如果A有多个构造函数,其参数个数不等,则可以通过在全局添加...来创建可变参数。

正如@Howard所说,尽管如此,您应该添加一个约束,以便该类似乎不能从其实际不存在的参数中构造。

1

如果样本中的stringstd::string,根本不在乎:默认提供的副本和移动呼叫各自在成员中。并std::string有复制和移动都执行,以便临时移动,变量被复制。

没有必要定义特定的副本并移动ctor并进行赋值。 你可以只用构造

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=需要在内部移动的值。如果它来自一个临时的被移动,如果它来自一个变量被复制。 复制和移动之间的差异需要不同的构造,但如果我们得到一个作为

class B: public A 
{ 
... 
}; 

没有必要覆盖任何东西,因为默认拷贝构造函数对于B要求A中的副本,而默认的举动因为B调用A的移动,并且B的所有默认分配操作符调用唯一为A定义的操作符(移动或复制取决于已转发的内容)。