C++ 当复制elison失败时,是否有方法防止移动构造函数后跟移动赋值运算符?
在这种情况下,我想用一个参数调用一个函数,并将结果返回到同一个参数中C++ 当复制elison失败时,是否有方法防止移动构造函数后跟移动赋值运算符?,c++,c++11,c++14,C++,C++11,C++14,在这种情况下,我想用一个参数调用一个函数,并将结果返回到同一个参数中 foo=f(foo); 另外,我假设参数x非常大,所以我不想调用它的复制构造函数,而是调用它的移动构造函数。最后,我不想通过引用传递参数,因为我想用另一个函数g组合函数f。所以,这样的事情, foo=g(f(foo)); 这是可能的。现在,有了移动语义,这几乎是可能的,如下程序所示 #包括 结构Foo{ Foo(){ std::cout如果您使用一点间接寻址和编译器优化,就可以不用移动: void do_f(Foo &am
foo=f(foo);
另外,我假设参数x
非常大,所以我不想调用它的复制构造函数,而是调用它的移动构造函数。最后,我不想通过引用传递参数,因为我想用另一个函数g
组合函数f
。所以,这样的事情,
foo=g(f(foo));
这是可能的。现在,有了移动语义,这几乎是可能的,如下程序所示
#包括
结构Foo{
Foo(){
std::cout如果您使用一点间接寻址和编译器优化,就可以不用移动:
void do_f(Foo & foo); // The code that used to in in f
inline Foo f(Foo foo)
{
do_f(foo);
return foo; // This return will be optimized away due to inlining
}
在我看来,您需要的不是一种通过函数调用来回移动值的机制,因为引用可以充分地做到这一点,而是一种以这种方式组合函数的设备
template <void f(Foo &), void g(Foo &)>
void compose2(Foo &v){
f(v);
g(v);
}
模板
无效成分2(Foo&v){
f(v);
g(v);
}
当然,您可以在参数类型上使其更通用
template <typename T, void f(T&), void (...G)(T&)>
void compose(T &v){
f(v);
compose2<T,G...>(v);
}
template <typename T>
void compose(Foo &){
}
模板
无效组合(T&v){
f(v);
成分2(v);
}
模板
无效组合(Foo&){
}
例如:
#include <iostream>
//... above template definitions for compose elided
struct Foo {
int x;
};
void f(Foo &v){
v.x++;
}
void g(Foo &v){
v.x *= 2;
}
int main(){
Foo v = { 9 };
compose<Foo, f, g, f, g>(v);
std::cout << v.x << "\n"; // output "42"
}
#包括
//…上面省略了compose的模板定义
结构Foo{
int x;
};
无效f(Foo&v){
v、 x++;
}
无效g(Foo&v){
v、 x*=2;
}
int main(){
Foo v={9};
撰写(五);
std::cout根据@MooingDuck的建议,实际上可以从函数返回一个rref。一般来说,这是一个非常糟糕的主意,但由于内存分配在函数之外,所以这就不是问题了。然后,移动的次数就大大减少了。不幸的是,如果有人试图将结果分配给一个rref,这将导致未定义的行为。所有代码和结果如下
对于单参数情况:
#包括
结构Foo{
//添加一些数据以查看它是否正确移动
int数据;
Foo():数据(0){
std::cout So,赋值在标准下不能省略。通过将中间值存储到Foo f2
和Foo f3
而不是返回到Foo
中,我们将移动操作总数从5减少到3。@Agnew暗示使用Foo
而不是Foo&
参数实际上增加了将move
计数减少2,因为函数参数在任何情况下都不会被省略到输出中。如果执行return foo;
而不是return std::move(foo);
在f()和g()中,会发生什么情况?这应该允许发生NRVO。它不会修复foo=f(std::move(foo));
但它应该避免在foo=g(f(std::move(foo))中指定一个移动
@FredericLachasse一般来说,这是个好主意,但在这种情况下,代码不执行NRVO,然后调用复制构造函数。问题是我们返回一个函数参数,标准说NRVO在12.8.31中不适用。如果我们在f或g中返回一个新变量,那么我100%同意你的意见。如果如果返回的是输入,可能它们应该通过rref而不是值返回?这将省去移动,但会使意外UB更容易。@MooingDuck很有趣,这似乎是可行的。一般来说,我不想通过rref返回,因为在函数退出时内存会被释放。但是,由于内存是在函数调用外部分配的,因此似乎这样做是对的。不过,我不理解你的部分评论。什么是“意外UB”?是内联还是NRVO?我认为NRVO将优化返回值,内联将用调用do\u f(foo)取代调用f(foo)
,不是吗?好吧,其中一个,这取决于你看待它的方式。如果你先看内联,它会用Foo temp\u Foo=Foo;do\u f(temp\u Foo);Foo=temp\u Foo;
编译器会优化到do\u f(Foo)来代替调用
。但您可以从另一个角度看,首先使用NRVO优化返回,然后进行内联。在这两种情况下,生成的代码都是相同的。NRVO将不会应用于函数参数。即,对象foo
将移动到返回值中,并且该移动可能不会被忽略。从技术上讲,是的。我真的希望这项工作也有多个参数,这有点像移动目标帖子。我在问题的更新中对此做了更多解释。没有必要将f
和g
作为模板参数。只需让compose
使用std::initializer\u list
,然后调用compose({f,g,f,g},v)
。通过将T
制作成模板参数包,可以很容易地扩展到处理多个参数。(但我可能遗漏了一些东西。如果这种形式的组合是可行的,那么为什么不直接编写f(v);g(v);f(v);g(v);
?)@hvd OP希望能够编写这些程序,也许是为了将编写的代码传递给其他代码?@hvd,如果有需要改进或修复的地方,请随意编辑我的答案。@didierc在这种情况下,我会使用lamba:您可以传递[](Foo&v){f(v);g(v);f(v);g(v);}
对任何函数执行void(*)(Foo&)
。我想这也是对OP的一点评论,但不仅仅是对你。我不清楚你的答案是否是解决OP问题的最佳方式,但部分原因是我不清楚问题的原因。我不想编辑,我可能完全误解了OP的原因。
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
void do_f(Foo & foo); // The code that used to in in f
inline Foo f(Foo foo)
{
do_f(foo);
return foo; // This return will be optimized away due to inlining
}
template <void f(Foo &), void g(Foo &)>
void compose2(Foo &v){
f(v);
g(v);
}
template <typename T, void f(T&), void (...G)(T&)>
void compose(T &v){
f(v);
compose2<T,G...>(v);
}
template <typename T>
void compose(Foo &){
}
#include <iostream>
//... above template definitions for compose elided
struct Foo {
int x;
};
void f(Foo &v){
v.x++;
}
void g(Foo &v){
v.x *= 2;
}
int main(){
Foo v = { 9 };
compose<Foo, f, g, f, g>(v);
std::cout << v.x << "\n"; // output "42"
}
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