简体   繁体   English

两步复制省略以捕获构造函数调用中的右值作为实例变量

[英]Two-step copy elision to capture rvalue in constructor call as instance variable

I am trying to get an rvalue instance of this class: 我正在尝试获取此类的rvalue实例:

#include <iostream>
#define msg(x) std::cout << x " constructor\n"

struct X {
    int i;
    X(int i) : i(i) {msg("X");}
    X(const X& x) : i(x.i) {std::cout << "X copy\n";}
    X(X&& x) {std::swap(i, x.i); std::cout << "X move\n";}
};

into instance variable x of this class: 到此类的实例变量x中:

struct A {
    X x;
    A(X x) : x(x) {msg("A");}
};

like so: 像这样:

int main() {
    A a(X(1));
    std::cout << a.x.i << "\n\n";
}

without any copies or moves being made . 没有任何复制或移动

According to these references, 根据这些参考资料,

and many many posts on SO ( so please read to the end before flagging as duplicate ), I should rely on copy elision , whose conditions should be satisfied if I pass by value . 以及关于SO的许多帖子( 因此,在标记为重复项之前,请先阅读全文 ),我应该依靠复制省略 ,如果按值传递,应该满足其条件。 Note that there are two copy elisions required, namely: 请注意,需要两个副本省略号:

constructor call -> constructor local variable -> instance variable

as can be seen when turning copy elision off (compile with g++-4.8 -std=c++11 -fno-elide-constructors ): 如关闭复制删除功能所见(使用g++-4.8 -std=c++11 -fno-elide-constructors编译):

X constructor
X move
X copy
A constructor
1

So there is one move step and one copy step, which should both go away if I turn copy elision on (compile with g++-4.8 -std=c++11 -O3 ): 因此,有一个move步骤和一个copy步骤,如果我打开复制省略功能(使用g++-4.8 -std=c++11 -O3编译),那么这两个步骤都应该消失:

X constructor
X copy
A constructor
1

Bummer, the copy step remained! Bummer, copy步骤仍然存在!


Can I get any better with any other variation of std::move() , std::forward or passing as rvalue-reference ? 使用std::move()std::forward或作为rvalue-reference传递的任何其他变体,我可以获得更好的结果吗?

struct B {
    X x;
    B(X x) : x(std::move(x)) {msg("B");}
};

struct C {
    X x;
    C(X x) : x(std::forward<X>(x)) {msg("C");}
};

struct D {
    X x;
    D(X&& x) : x(std::move(x)) {msg("D");}
};

int main() {
    B b(X(2));
    std::cout << b.x.i << "\n\n";

    C c(X(3));
    std::cout << c.x.i << "\n\n";

    D d(X(4));
    std::cout << d.x.i << "\n\n";
}

which produces the output: 产生输出:

X constructor
X move
B constructor
2

X constructor
X move
C constructor
3

X constructor
X move
D constructor
4

OK, I turned the copy into a move , but this is not satisfactory! OK,我把copy到一个move但这并不理想!


Next, I tried to make the instance variable x a reference X& : 接下来,我尝试使实例变量x成为引用X&

struct E {
    X& x;
    E(X x) : x(x) {msg("E");}
};

int main() {
    E e(X(5));
    std::cout << e.x.i << "\n\n";
}

which produces: 产生:

X constructor
E constructor
1690870696

Bad idea! 馊主意! I got rid of the move but the rvalue instance that x was referencing to got destroyed under my seat, so the last line prints garbage instead of 5 . 我摆脱了这一move但是x所引用的rvalue实例在我的座位下被破坏了,所以最后一行显示的是垃圾而不是5 Two notes: 两个注意事项:

  • g++-4.8 didn't warn me of anything, even with -pedantic -Wall -Wextra g++-4.8没有警告我任何事情,即使使用-pedantic -Wall -Wextra
  • The program prints 5 when compiled with -O0 -O0编译时,程序打印5

So this bug may go unnoticed for quite a while! 因此,此错误可能会在相当长的一段时间内被忽略!


So, is this a hopeless case? 那么,这是没有希望的情况吗? Well no: 好吧:

struct F {
    X& x;
    F(X& x) : x(x) {msg("F");}
};

int main() {
    X x(6);
    F f(x);
    std::cout << f.x.i << "\n";
}

prints: 印刷品:

X constructor
F constructor
6

Really? 真? No fancy new C++11 features, no copy elision at the discretion of the compiler, just plain old FORTRAN66-style pass-by-reference does what I want and probably will perform best? 没有花哨的C++11新功能,没有由编译器决定的复制省略,只是普通的FORTRAN66样式的按引用传递了我想要的,并且可能会表现最佳?

So here are my questions: 所以这是我的问题:

  • Is there any way one can get this to work for rvalues ? 有什么方法可以rvalues适用于rvalues Did I miss any features? 我错过任何功能吗?
  • Is the lvalue -reference version really the best, or are there hidden costs in the X x(6) step? lvalue引用版本确实是最好的,还是X x(6)步骤中存在隐藏成本?
  • Could there be any inconveniences introduced by x living on after the construction of f ? 在构造f之后, x存在会带来任何不便吗?
  • Could I pay a data locality penalty for using the lvalue reference to an external instance? 我是否可以使用对外部实例的lvalue引用来支付数据局部性损失?

Without going into too much detail of your question, copy elision is basically used as much as possible. 无需过多讨论您的问题,基本上尽量使用复制省略。 Here's a quick demo: 这是一个快速演示:

#include <iostream>
#include <utility>

struct X
{
    int n_;

    explicit X(int n) : n_(n) { std::cout << "Construct: " << n_ << "\n"; }
    X(X const & rhs) : n_(rhs.n_) { std::cout << "X copy:" << n_ << "\n"; }
    X(X && rhs) : n_(rhs.n_) { rhs.n_ = -1; std::cout << "X move:" << n_ << "\n"; }
   ~X() { std::cout << "Destroy: " << n_ << "\n"; }
};

struct A
{
    explicit A(X x) : x_(std::move(x)) {};
    X x_;
};

struct B
{
    X x;
};

int main()
{
    A a(X(12));
    B b { X(24) };
}

This produces : 这将产生

Construct: 12
X move:12
Destroy: -1
Construct: 24
Destroy: 24
Destroy: 12

The one move in x_(std::move(x)) is not elidable, since it doesn't involve a function return. x_(std::move(x))的一招是不可消除的,因为它不涉及函数返回。 But that's pretty good anyway. 但这还是很好。 And notice how the aggregate b is indeed initialized "in-place". 并注意聚合b确实是“就地”初始化的。


Your example F shows that you're willing to expose the coupling of X to its ambient class. 您的示例F表明,您愿意公开X与其环境等级的耦合。 In that case, you could make a special constructor for A that constructs the X directly: 在这种情况下,您可以为A创建一个特殊的构造函数,该构造函数直接构造X

struct A
{
    explicit A(X x) : x_(std::move(x)) {};
    X x_;

    // Better approach
    struct direct_x_t {};
    static direct_x_t direct_x;

    // In our case, this suffices:
    A(direct_x_t, int n) : x_(n) {}

    // Generally, a template may be better: (TODO: add SFINAE control)
    template <typename ...Args> A(direct_x_t, Args &&... args)
    : x_(std::forward<Args>(args)...) {}
};

Usage: 用法:

A a(A::direct_x, 36);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM