根据 @MooingDuck 的建议,从函数中返回rref实际上是可能的。通常情况下,这将是一个非常糟糕的想法,但由于内存在函数外部分配,因此这不成问题。然后,移动次数大大减少。不幸的是,如果有人试图将结果分配给rref,则会导致未定义的行为。以下是所有代码和结果。
对于单个参数的情况:
#include <iostream>
struct Foo {
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
data = x.data;
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
data = x.data;
std::cout << "copy assignment" << std::endl;
return *this;
}
};
Foo && f(Foo && foo) {
std::cout << "Called f: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
Foo && g(Foo && foo) {
std::cout << "Called g: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
int main() {
Foo foo(5);
foo = f(std::move(foo));
std::cout << "Finished with f(foo)" << std::endl;
foo = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo))" << std::endl;
Foo foo2 = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo)) a second time" << std::endl;
std::cout << "foo2.data = " << foo2.data << std::endl;
Foo && foo3 = g(f(Foo(4)));
std::cout << "foo3.data = " << foo3.data << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
这将生成
constructor
Called f: foo.data = 5
move assignment
Finished with f(foo)
Called f: foo.data = 5
Called g: foo.data = 5
move assignment
Finished with g(f(foo))
Called f: foo.data = 5
Called g: foo.data = 5
move
Finished with g(f(foo)) a second time
foo2.data = 5
constructor
Called f: foo.data = 4
Called g: foo.data = 4
destructor
foo3.data = 4. If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
在多参数情况下
#include <tuple>
#include <iostream>
#include <utility>
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices =
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}
struct Foo {
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
std::cout << "copy assignment" << std::endl;
return *this;
}
};
std::tuple <Foo&&,Foo&&> f(Foo && x,Foo && y) {
std::cout << "Called f: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
std::tuple <Foo&&,Foo&&> g(Foo && x,Foo && y) {
std::cout << "Called g: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
int main() {
Foo x(5),y(6);
std::tie(x,y) = f(std::move(x),std::move(y));
std::cout << "Finished with f(x,y)" << std::endl;
std::tie(x,y) = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y))" << std::endl;
std::tuple <Foo,Foo> x_y = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y)) a second time" << std::endl;
std::cout << "(x.data,y.data) = (" << std::get <0>(x_y).data << ',' <<
std::get <1> (x_y).data << ')' << std::endl;
std::tuple <Foo&&,Foo&&> x_y2 = apply(g,f(Foo(7),Foo(8)));
std::cout << "(x2.data,y2.data) = (" << std::get <0>(x_y2).data << ',' <<
std::get <1> (x_y2).data << ')' << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
这个生成
constructor
constructor
Called f: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with f(x,y)
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with g(f(x,y))
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move
move
Finished with g(f(x,y)) a second time
(x.data,y.data) = (5,6)
constructor
constructor
Called f: (x.data,y.data) = (7,8)
Called g: (x.data,y.data) = (7,8)
destructor
destructor
(x2.data,y2.data) = (7,8). If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
destructor
destructor
Foo f2
和Foo f3
而不是返回到foo
中,我们将总移动操作次数从5降低到3。@Agnew的隐含建议是使用Foo
而不是Foo&&
参数,实际上会增加2个move
计数,因为函数参数在任何情况下都不能省略为输出。 - Yakk - Adam Nevraumontreturn foo;
而非return std::move(foo);
会发生什么?这将使NRVO发生。它无法解决foo=f(std::move(foo));
,但应该避免在foo=g(f(std::move(foo)));
中进行一次移动赋值操作。 - Frederic Lachasse