我遇到一种情况,我想用参数调用函数并将结果返回到同一参数
foo = f(foo);
另外,我假设参数x
很大,所以我不想调用其副本构造函数,而要调用其move构造函数。最后,我不想通过引用传递参数,因为我想将函数f
与另一个函数组成g
。因此,像
foo = g(f(foo));
是可能的。现在,借助移动语义,这几乎可以实现,如以下程序所示
#include <iostream>
struct Foo {
Foo() {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
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;
}
};
Foo f(Foo && foo) {
std::cout << "Called f" << std::endl;
return std::move(foo);
}
Foo g(Foo && foo) {
std::cout << "Called g" << std::endl;
return std::move(foo);
}
int main() {
Foo foo;
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;
}
该程序的输出为:
constructor
Called f
move
move assignment
destructor
Finished with f(foo)
Called f
move
Called g
move
move assignment
destructor
destructor
Finished with g(f(foo))
destructor
这是有道理的。现在,令我困扰的是,当我们f
第一次调用组合时,移动构造函数后面是移动赋值运算符。理想情况下,我想使用复制Elison来防止调用任何这些构造函数,但是我不确定如何调用。具体来说,功能f
和g
电话std::move
上foo
的,否则副本,不动,构造函数被调用。这在C ++标准的12.8.31和12.8.32节中指定。具体来说,
当满足某些条件时,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用,也允许实现忽略类对象的复制/移动构造。在这种情况下,实现将忽略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本来应该以较晚的时间发生。没有优化就销毁。在以下情况下允许复制/移动操作的这种省略,称为复制删除(可以合并以消除多个副本):
—在具有类返回类型的函数的返回语句中,当表达式是具有与函数返回类型相同的cvunqualified类型的非易失性自动对象(函数或catch子句参数除外)的名称时,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作
由于返回函数自变量,因此不会得到复制错误。此外:
当满足或将要执行复制操作的省略标准时,除非源对象是函数参数,并且要复制的对象由左值指定,选择重载构造函数的重载分辨率为首先执行,就好像该对象是由右值指定的。如果重载解析失败,或者所选构造函数的第一个参数的类型不是对该对象类型的右值引用(可能是cv限定),则将对象视为左值,再次执行重载解析。[注意:无论是否出现复制省略,都必须执行此两阶段重载解决方案。如果不执行省略操作,它将确定要调用的构造函数,并且即使取消了调用,所选的构造函数也必须可访问。—尾注]
由于我们返回了函数参数,因此我们返回了一个l值,因此我们不得不使用std::move
。现在,在一天结束时,我只希望将内存移回参数中,同时调用move构造函数和move赋值运算符似乎太多了。感觉应该有一个单一的举动或复制权。有没有办法做到这一点?
在对@didierc答案的更长回应中,从评论上讲,从技术上讲,是的,这将适用于这种情况。同时,更大的目标是允许具有多个返回值的函数以不复制任何内容的方式组合在一起。我也可以使用移动语义来做到这一点,但这需要C ++ 14的技巧才能起作用。许多步骤也加剧了该问题。但是,从技术上讲,没有副本。具体来说:
#include <tuple>
#include <iostream>
#include <utility>
// This comes from the N3802 proposal for C++
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{});
}
// Now, for our example
struct Foo {
Foo() {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
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" << std::endl;
return std::make_tuple <Foo,Foo> (std::move(x),std::move(y));
}
std::tuple <Foo,Foo> g(Foo && x,Foo && y) {
std::cout << "Called g" << std::endl;
return std::make_tuple <Foo,Foo> (std::move(x),std::move(y));
}
int main() {
Foo x,y;
std::tie(x,y) = f(std::move(x),std::move(y));
std::cout << "Finished with f(foo)" << std::endl;
std::tie(x,y) = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(foo))" << std::endl;
}
这产生
constructor
constructor
Called f
move
move
move assignment
move assignment
destructor
destructor
Finished with f(foo)
Called f
move
move
Called g
move
move
move assignment
move assignment
destructor
destructor
destructor
destructor
Finished with g(f(foo))
destructor
destructor
基本上,发生与上述相同的问题:如果移动分配消失了,我们将得到很好的移动分配。
根据@MooingDuck的建议,实际上可以从函数中返回一个rref。通常,这是一个非常糟糕的主意,但是由于内存是在函数外部分配的,因此它不是问题。然后,移动次数大大减少。不幸的是,如果有人尝试将结果分配给rref,这将导致未定义的行为。所有代码和结果如下。
对于单参数情况:
#include <iostream>
struct Foo {
// Add some data to see if it gets moved correctly
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;
// Now, break it.
Foo && foo3 = g(f(Foo(4)));
// Notice that the destuctor for Foo(4) occurs before the following line.
// That means that foo3 points at destructed memory.
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>
// This comes from the N3802 proposal for C++
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{});
}
// Now, for our example
struct Foo {
// Add some data to see if it gets moved correctly
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;
// Now, break it.
std::tuple <Foo&&,Foo&&> x_y2 = apply(g,f(Foo(7),Foo(8)));
// Notice that the destuctors for Foo(7) and Foo(8) occur before the
// following line. That means that x_y2points at destructed memory.
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
根据@MooingDuck的建议,实际上可以从函数中返回一个rref。通常,这是一个非常糟糕的主意,但是由于内存是在函数外部分配的,因此它不是问题。然后,移动次数大大减少。不幸的是,如果有人尝试将结果分配给rref,这将导致未定义的行为。所有代码和结果如下。
对于单参数情况:
#include <iostream>
struct Foo {
// Add some data to see if it gets moved correctly
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;
// Now, break it.
Foo && foo3 = g(f(Foo(4)));
// Notice that the destuctor for Foo(4) occurs before the following line.
// That means that foo3 points at destructed memory.
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>
// This comes from the N3802 proposal for C++
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{});
}
// Now, for our example
struct Foo {
// Add some data to see if it gets moved correctly
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;
// Now, break it.
std::tuple <Foo&&,Foo&&> x_y2 = apply(g,f(Foo(7),Foo(8)));
// Notice that the destuctors for Foo(7) and Foo(8) occur before the
// following line. That means that x_y2points at destructed memory.
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
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句