我想编写几个递归交互的合并函数,我认为它们应该有签名:T&& merge_XYZ(T&& a, T&& b);
它们倾向于以递归方式使用,例如:
return merge_XYZ( std::move(x), std::move(y) );
这几个合并函数中的每一个都会窃取其中一个输入的内容,并将这些内容注入另一个输入并返回结果。通常,它们将具有x
和y
,它们是rvalue引用的名称,因此应该由std::move
转换回rvalue引用(如果我错了,请纠正我)。
但很少,他们有x
和/或y
是对其内容不得被盗的对象的引用。我绝对不想写这些函数的替代非窃取版本。相反,我希望呼叫者在这些极少数情况下处理这个问题。所以我的主要问题是,正确的方法是显式调用复制构造,例如:
T temp = merge_QRS( T(x), T(y) ); // use x and y without stealing yet
return merge_XYZ( merge_MNO( std::move(x), std::move(y) ), std::move(temp) );
主要问题:T(x)
是否是强制在此时创建临时副本的正确方法?
其他问题:
是T temp =
是确保在调用merge_QRS
之前调用上述代码中的merge_MNO
的正确方法,但是以其他方式便宜地将临时代码转发到{{1}的第一个操作数中}}?如果我使用merge_XYZ
而不是在T&& temp
的生命周期之后最终持有指向修改后的T(x)
的指针?
T(x)
是否将正确的返回类型(而不是T&&
)链接在一起?
上述内容与以下相比如何:
T
假设T tx = x;
T&& temp = merge_QRS( std::move(tx), T(y) ); // use x and y without stealing yet
return merge_XYZ( merge_MNO( std::move(x), std::move(y) ), std::move(temp) );
将修改tx并返回一个rvalue引用,那么该行为是否全部定义了?
写这个问题可能有助于我意识到我可以把两种不应该混合的情况混合在一起:你不想偷的对象和你不想偷的对象然而< / strong>即可。对于我不想偷的对象,我的原始merge_QRS
是否正确(仅在同一表达式中使用)?但在我尝试的情况下,我应该有以下内容:
merge_QRS( T(y), T(x))
我想我可能仍然对窃取内容与窃取身份感到困惑。如果我通过T tx = x; // Make copies which can be stolen from
T ty = y;
return merge_XYZ( merge_MNO( std::move(x), std::move(y) ),
merge_QRS( std::move(tx), std::move(ty) ) );
返回,除了盗取另一个输入的内容之外,我还窃取了一个输入的标识。我什么时候可以偷走身份?如果我从T&&
返回,我永远不会窃取身份,有时候没有窃取身份是低效的。
答案 0 :(得分:4)
主要问题:T(x)是否是强制在该点创建临时副本的正确方法?
是
T temp =确保在调用merge_MNO之前确保在上面的代码中调用merge_QRS的正确方法,但是以其他方式便宜地将临时代码转发到merge_XYZ的第一个操作数中吗?
是
如果我使用T&amp;&amp;相反,它会在T(x)的生命周期之后最终持有指向修改后的T(x)的指针吗?
是。这是悬挂式引用,遗憾的是编译器无法捕获。
T&amp;&amp;正确的返回类型(而不是T)将很多这些链接在一起?
老实说,这对我来说并不好闻。
您可能希望重新考虑您的数据模型,使其更具标准性,即:
T merge(T x, T y)
{
// do some merging
return x;
}
Copy-elision和RVO将消除任何冗余副本。现在,您可以移动项目,传递副本或传递临时对象。只有一个逻辑要维护,你的代码有价值语义...... 总是更好(TM)。
答案 1 :(得分:0)
tl; dr:您可以使用值语义:
考虑
struct X
{
X() = default;
X(int y) : a(y) {}
X(X &&r) : a(r.a) { std::cout << "move X..."; }
X(X const &r) : a(r.a) { std::cout << "copy X..."; }
int a;
};
foo
:
X&& foo(X &&a) { return std::move(a); }
和bar
:
X bar(X a) { return a; }
然后执行以下代码时:
std::cout << "foo:\n";
X x{ 55 };
std::cout << "a: ";
X && a = foo(std::move(x)); // fine
std::cout << "\nb: ";
X && b = foo(X(x)); // !! dangling RV is not prvalue but xvalue
std::cout << "\nc: ";
X c = foo(std::move(x)); // fine, copy
std::cout << "\nd: ";
X d = foo(X(x)); // fine
std::cout << "\ne: ";
X && e = foo(X{ 12 }); // !! dangling...
std::cout << "\nf: ";
X f = foo(X{ 12 }); // fine
std::cout << "\n\nbar:\n";
X y{ 55 };
std::cout << "a: ";
X && q = bar(std::move(y)); // fine
std::cout << "\nb: ";
X && r = bar(y); // no explict copy required, supported by syntax
std::cout << "\nc: ";
X s = bar(std::move(y)); // fine
std::cout << "\nd: ";
X t = bar(y); // fine, no explict copy required either
std::cout << "\ne: ";
X && u = bar(X{ 12 }); // fine
std::cout << "\nf: ";
X v = bar(X{ 12 }); // fine
std::cout << "\n";
我们获得
foo:
a:
b: copy X...
c: move X...
d: copy X...move X...
e:
f: move X...
bar :
a: move X...move X...
b: copy X...move X...
c: move X...move X...
d: copy X...move X...
e: move X...
f: move X...
在VS 2015和g ++ 5.2上。
因此,所有制作的副本(bar
)都在案例b和d中,这是理想的行为,但你可以摆脱可能悬空的引用,每次操作1-2次移动(其中甚至可以在某些情况下优化afaik。