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。从技术上讲,我不确定这是否被视为复制省略。
我正在尝试让复制省略适用于要返回的对象的字段。
示例代码:
#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。从技术上讲,我不确定这是否被视为复制省略。