当复制elison失败时,是否有办法防止移动构造函数后跟移动分配运算符?

wyer33

我遇到一种情况,我想用参数调用函数并将结果返回到同一参数

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来防止调用任何这些构造函数,但是我不确定如何调用。具体来说,功能fg电话std::movefoo的,否则副本,不动,构造函数被调用。这在C ++标准的12.8.31和12.8.32节中指定。具体来说,

当满足某些条件时,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用,也允许实现忽略类对象的复制/移动构造。在这种情况下,实现将忽略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本来应该以较晚的时间发生。没有优化就销毁。在以下情况下允许复制/移动操作的这种省略,称为复制删除(可以合并以消除多个副本):

—在具有类返回类型的函数的返回语句中,当表达式是具有与函数返回类型相同的cvunqualified类型的非易失性自动对象(函数或catch子句参数除外)的名称时,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作

由于返回函数自变量,因此不会得到复制错误。此外:

当满足或将要执行复制操作的省略标准时,除非源对象是函数参数,并且要复制的对象由左值指定,选择重载构造函数的重载分辨率为首先执行,就好像该对象是由右值指定的。如果重载解析失败,或者所选构造函数的第一个参数的类型不是对该对象类型的右值引用(可能是cv限定),则将对象视为左值,再次执行重载解析。[注意:无论是否出现复制省略,都必须执行此两阶段重载解决方案。如果不执行省略操作,它将确定要调用的构造函数,并且即使取消了调用,所选的构造函数也必须可访问。—尾注]

由于我们返回了函数参数,因此我们返回了一个l值,因此我们不得不使用std::move现在,在一天结束时,我只希望将内存移回参数中,同时调用move构造函数和move赋值运算符似乎太多了。感觉应该有一个单一的举动或复制权。有没有办法做到这一点?

编辑1

在对@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

基本上,发生与上述相同的问题:如果移动分配消失了,我们将得到很好的移动分配。

编辑2

根据@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
wyer33

根据@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] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

具有复制/移动分配运算符的enable_if

来自分类Dev

C ++移动副本构造函数和移动分配运算符

来自分类Dev

移动分配运算符和移动构造函数之间的区别?

来自分类Dev

移动构造函数和移动分配。基类的运算符

来自分类Dev

移动构造函数并移动类的赋值运算符

来自分类Dev

C ++移动语义与智能指针相关的复制构造函数和赋值运算符

来自分类Dev

删除默认C ++复制和移动构造函数和赋值运算符的缺点?

来自分类Dev

为简单结构定义哪个复制/移动构造函数/运算符?

来自分类Dev

删除默认C ++复制和移动构造函数和赋值运算符的缺点?

来自分类Dev

删除复制构造函数或复制分配运算符是否算作“用户声明”?

来自分类Dev

使用哪个:移动分配运算符与复制分配运算符

来自分类Dev

隐式移动构造函数和赋值运算符

来自分类Dev

根据move构造函数实现复制分配运算符

来自分类Dev

C ++移动语义:为什么调用复制分配运算符=(&)而不是移动分配运算符=(&&)?

来自分类Dev

C ++移动语义:为什么调用复制分配运算符=(&)而不是移动分配运算符=(&&)?

来自分类Dev

复制构造函数和运算符是否必须?

来自分类Dev

移动分配运算符中的异常

来自分类Dev

移动分配运算符未被调用

来自分类Dev

为什么当用户提供移动构造函数或移动分配时,复制构造函数和复制分配会被删除?

来自分类Dev

如何将unique_ptr的移动构造函数和运算符实现为类的私有成员

来自分类Dev

移动构造函数与移动分配

来自分类Dev

c ++(为什么)确实移动构造函数删除运算符=

来自分类Dev

如何在移动分配运算符中调用析构函数?

来自分类Dev

如果我还使用复制构造函数和重载=运算符,是否需要析构函数?

来自分类Dev

移动共享指针的构造方法和=运算符

来自分类Dev

复制构造函数和复制赋值运算符?

来自分类Dev

使用动态绑定为类移动分配运算符

来自分类Dev

“移动构造函数”是否总是比“复制构造函数”更高效?

来自分类Dev

是私有移动构造函数来防止移动吗?

Related 相关文章

  1. 1

    具有复制/移动分配运算符的enable_if

  2. 2

    C ++移动副本构造函数和移动分配运算符

  3. 3

    移动分配运算符和移动构造函数之间的区别?

  4. 4

    移动构造函数和移动分配。基类的运算符

  5. 5

    移动构造函数并移动类的赋值运算符

  6. 6

    C ++移动语义与智能指针相关的复制构造函数和赋值运算符

  7. 7

    删除默认C ++复制和移动构造函数和赋值运算符的缺点?

  8. 8

    为简单结构定义哪个复制/移动构造函数/运算符?

  9. 9

    删除默认C ++复制和移动构造函数和赋值运算符的缺点?

  10. 10

    删除复制构造函数或复制分配运算符是否算作“用户声明”?

  11. 11

    使用哪个:移动分配运算符与复制分配运算符

  12. 12

    隐式移动构造函数和赋值运算符

  13. 13

    根据move构造函数实现复制分配运算符

  14. 14

    C ++移动语义:为什么调用复制分配运算符=(&)而不是移动分配运算符=(&&)?

  15. 15

    C ++移动语义:为什么调用复制分配运算符=(&)而不是移动分配运算符=(&&)?

  16. 16

    复制构造函数和运算符是否必须?

  17. 17

    移动分配运算符中的异常

  18. 18

    移动分配运算符未被调用

  19. 19

    为什么当用户提供移动构造函数或移动分配时,复制构造函数和复制分配会被删除?

  20. 20

    如何将unique_ptr的移动构造函数和运算符实现为类的私有成员

  21. 21

    移动构造函数与移动分配

  22. 22

    c ++(为什么)确实移动构造函数删除运算符=

  23. 23

    如何在移动分配运算符中调用析构函数?

  24. 24

    如果我还使用复制构造函数和重载=运算符,是否需要析构函数?

  25. 25

    移动共享指针的构造方法和=运算符

  26. 26

    复制构造函数和复制赋值运算符?

  27. 27

    使用动态绑定为类移动分配运算符

  28. 28

    “移动构造函数”是否总是比“复制构造函数”更高效?

  29. 29

    是私有移动构造函数来防止移动吗?

热门标签

归档