考虑这些课程:
#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
仅存在于可视化复制/移动调用的执行方式。
答案 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)
如果样本中的string
为std::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(根据转发的内容移动或复制)。