C++ 字段复制省略

C++ copy elision of fields

我正在尝试让复制省略适用于要返回的对象的字段。

示例代码:

#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;
}

我编译: g++ -std=c++17 test.cpp -o test.exe

输出:

A constructed
A copied
B constructed
1

B 是就地建造的。为什么不是A?我至少希望它是移动构造的,但它是复制的。

有没有办法在要返回的 B 中就地构建 A?怎么样?

A 构建 B 涉及复制 A - 它在您的代码中如此说明。这与函数 return 中的复制省略无关,所有这些都发生在 B 的(最终)构造中。标准中没有任何内容允许省略(如 "breaking the as-if rule for")成员初始化列表中的复制构造。请参阅 [class.copy.elision] 了解可能违反假设规则的少数情况。

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

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

您将无法成功删除当前形式的临时文件。

虽然该语言确实试图限制临时对象的实例化 ("materialisation")(以一种标准规定的方式并且不影响假设规则),但有时您的临时必须具体化,它们包括:

[class.temporary]/2.1: - when binding a reference to a prvalue

你在这里,在构造函数参数中这样做。

事实上,如果您查看标准那段中的示例程序,它与您的几乎相同,它描述了如何不需要在 main 中创建临时文件然后复制到进入您的函数参数的新临时文件……但是为该函数参数创建的临时文件 。没有办法解决这个问题。

然后以通常的方式复制到成员。现在 as-if 规则开始生效,并且该规则没有例外,它允许 B 的构造函数的语义(包括呈现 "copied" 输出)按照您希望的方式进行更改。

您可以为此检查程序集输出,但我猜如果没有输出,就不需要实际执行任何复制操作,并且编译器可以在不违反假设规则的情况下删除您的临时文件(即在从您的 C++ 创建计算机程序时其活动的正常过程,这只是程序的抽象描述)。但一直都是这样,我想你已经知道了。

当然,如果您添加 B(A&& a) : a(std::move(a)) {},那么您 将对象移动 到成员中,但我想您也已经知道了。

我想出了如何做我想做的事情。

目的是 return 一个函数的多个值,其中 "work" 的数量最少。

我尽量避免将 return 值作为可写引用传递(以避免值突变和赋值运算符),因此我想通过将要 returned 的对象包装在一个结构中来做到这一点.

我以前成功过,所以我很惊讶上面的代码不起作用。

这确实有效:

#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

关键是删除 B 的所有构造函数。这启用了聚合初始化,这似乎是就地构造字段。结果,避免了复制 A。从技术上讲,我不确定这是否被视为复制省略。