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