C++ C++;字段的复制省略

C++ C++;字段的复制省略,c++,c++17,in-place,copy-elision,C++,C++17,In Place,Copy Elision,我试图让复制省略对要返回的对象的字段起作用 示例代码: #include <iostream> struct A { bool x; A(bool x) : x(x) { std::cout << "A constructed" << std::endl; } A(const A &other) : x(other.x) { std::cout << "A copied" &

我试图让复制省略对要返回的对象的字段起作用

示例代码:

#include <iostream>

struct A {
    bool x;
    A(bool x) : x(x) {
        std::cout << "A constructed" << std::endl;
    }
    A(const A &other) : x(other.x) {
        std::cout << "A copied" << std::endl;
    }
    A(A &&other) : x(other.x) {
        std::cout << "A moved" << std::endl;
    }
    A &operator=(const A &other) {
        std::cout << "A reassigned" << std::endl;
        if (this != &other) {
            x = other.x;
        }
        return *this;
    }
};

struct B {
    A a;
    B(const A &a) : a(a) {
        std::cout << "B constructed" << std::endl;
    }
    B(const B &other) : a(other.a) {
        std::cout << "B copied" << std::endl;
    }
    B(B &&other) : a(other.a) {
        std::cout << "B moved" << std::endl;
    }
    B &operator=(const B &other) {
        std::cout << "B reassigned" << std::endl;
        if (this != &other) {
            a = other.a;
        }
        return *this;
    }
};

B foo() {
    return B{A{true}};
}


int main() {
    B b = foo();
    std::cout << b.a.x << std::endl;
}
B是在适当的地方建造的。为什么不是?我至少希望它是移动构造的,但它是复制的


是否有一种方法也可以在待返回的B内就地构造a?如何?

a
构建
B
涉及复制
a
——它在代码中这样说。这与函数返回中的复制省略无关,所有这些都发生在
B
的(最终)构造中。标准中的任何内容都不允许删除成员初始化列表中的副本构造(如“违反”的“似乎规则”)。请参阅“似乎”规则可能被打破的少数情况

换句话说:当创建
bb{A{true}时,您会得到完全相同的输出。函数return同样好,但不是更好


如果你想移动
A
而不是复制,你需要一个构造函数
B(A&&)
(然后移动它来构造
A
成员)。

你将无法成功地删除当前形式的临时变量

虽然该语言试图限制临时文件的实例化(“具体化”)(以标准规定的方式,不影响“仿佛”规则),但仍有一些时候临时文件必须具体化,包括:

:-将引用绑定到prvalue时

在这里,在构造函数参数中就是这样做的

事实上,如果你看一下该标准段落中的示例程序,它与你的程序几乎相同,它描述了如何不必在
main
中创建临时程序,然后复制到一个新的临时程序,该临时程序进入你的函数参数中……但是临时程序是为该函数参数创建的。这是没有办法的

然后,以通常的方式向成员发送副本。现在,似乎规则开始生效了,这条规则没有任何例外,它允许
B
的构造函数的语义(包括呈现
“复制的”
输出)以您希望的方式进行更改

您可以为此检查程序集输出,但我想如果没有输出,就不需要实际执行任何复制操作,编译器可以省略您的临时代码,而不会违反“仿佛”规则(也就是说,在计算机程序的正常运行过程中,从C++中,这只是程序的抽象描述)。但是,情况总是这样,我想你已经知道了。
当然,如果您添加a
B(a&&a):a(std::move(a)){}
,那么您可以将对象移动到成员中,但我想您也已经知道了。

我已经想出了如何做我想要的事情

其目的是用最少的“工作量”从函数返回多个值

我试图避免将返回值作为可写引用传递(以避免值变异和赋值运算符),因此我希望通过将要返回的对象包装到结构中来实现这一点

我以前在这方面做得很成功,所以我很惊讶上面的代码不起作用

这确实有效:

#include <iostream>

struct A {
    bool x;
    explicit A(bool x) : x(x) {
        std::cout << "A constructed" << std::endl;
    }
    A(const A &other) : x(other.x) {
        std::cout << "A copied" << std::endl;
    }
    A(A &&other) : x(other.x) {
        std::cout << "A moved" << std::endl;
    }
    A &operator=(const A &other) {
        std::cout << "A reassigned" << std::endl;
        if (this != &other) {
            x = other.x;
        }
        return *this;
    }
};

struct B {
    A a;
};

B foo() {
    return B{A{true}};
}


int main() {
    B b = foo();
    std::cout << b.a.x << std::endl;
}

关键是删除B的所有构造函数。这启用了聚合初始化,它似乎在适当的位置构造字段。因此,避免了复制a。从技术上讲,我不确定这是否被视为复制省略。

如果您希望编译器优化器更聪明并生成快速代码,请尝试启用它(比如
g++-std=c++17-O3…
)。调试构建(默认)是未优化的,并专注于提供良好的调试体验,而不是让代码快速运行。我们绝对不能期望新的(ish)临时物质化规则处理临时
a
?我自己对这些规则不太感兴趣(FWIW-O2不改变输出)@LightnessRacesBY-SA3.0我不认为有什么比会员初始化列表更重要的了,因为会员初始化列表不是违反“仿佛”规则的受制裁的地方之一。我想它已经实现了。事实上,是的,这一部分中的示例基本上是OP的程序heheSee我的答案。你删除了临时实现,因为你不再是binding它是一个参考(ctor arg)。基本上,不再有临时性的。这不是一个优化:它被烘焙到你的程序的实际意义中。这是一个很好的解决方案(只要
B
不需要更多的东西)
#include <iostream>

struct A {
    bool x;
    explicit A(bool x) : x(x) {
        std::cout << "A constructed" << std::endl;
    }
    A(const A &other) : x(other.x) {
        std::cout << "A copied" << std::endl;
    }
    A(A &&other) : x(other.x) {
        std::cout << "A moved" << std::endl;
    }
    A &operator=(const A &other) {
        std::cout << "A reassigned" << std::endl;
        if (this != &other) {
            x = other.x;
        }
        return *this;
    }
};

struct B {
    A a;
};

B foo() {
    return B{A{true}};
}


int main() {
    B b = foo();
    std::cout << b.a.x << std::endl;
}
A constructed
1