复制省略并移动构造函数
Copy elision and move constructor
考虑以下 Person
的定义:
struct Person
{
Person() { std::cout << "construct, "; }
Person(const Person&) { std::cout << "copy\n"; }
Person(Person&&) { std::cout << "move\n"; }
};
和 3 个不同的函数来创建 Person
:
Person create1()
{
std::cout << "create1: ";
Person p1{};
return p1;
}
Person create2()
{
std::cout << "create2: ";
if constexpr (true)
{
Person p1{};
return p1;
}
else
{
Person p2{};
return p2;
}
}
Person create3()
{
std::cout << "create3: ";
if constexpr (true)
{
return Person{};
}
else
{
return Person{};
}
}
最后,我调用创建函数如下:
int main()
{
Person p1 = create1();
Person p2 = create2();
Person p3 = create3();
return 0;
}
输出为:
create1: construct
create2: construct, move
create3: construct
困扰我的是 create2
的输出。如果在create1
和create3
中没有调用move构造函数,为什么在create2
中调用呢?
我正在使用 GCC 12.0.0。
编辑:Clang 13.0.0 不调用移动构造函数。
我认为它是常量表达式中 NRVO 的一个实例 cppreference says:
In constant expression and constant initialization, return value optimization (RVO) is guaranteed, however, named return value optimization (NRVO) is forbidden:
这与CWG 2278有关。
编辑
我重新考虑了:-)它是从main()
调用的,所以不能常量初始化。通过实际常量初始化,gcc 和 clang 都不会执行 NRVO。在本例中 (see godbolt):
struct Person {
bool was_moved;
constexpr Person() : was_moved{false} { }
constexpr Person(const Person&) : was_moved{false} { }
constexpr Person(Person&&) : was_moved{true} { }
};
constexpr Person create() {
if (true) {
Person p1;
return p1;
}
else {
Person p2;
return p2;
}
}
constexpr Person p = create();
int main() {
return p.was_moved;
}
p.was_moved
是 1.
至于为什么 gcc 在 create2()
中不执行 NRVO,一个答案可能是它不是强制性的,根据之前的 cppreference link。另一个可能是 NRVO 的 gcc 实现非常“脆弱”。例如,当只涉及一个命名变量时它起作用:
Person create4() {
Person p{};
if (true) {
return p;
}
else {
return p;
}
}
但是当整个东西被放入子范围时停止工作:
Person create5() {
{
Person p{};
if (true) {
return p;
}
else {
return p;
}
}
}
从 2012 年开始就存在关于这两个问题的 gcc 错误:Bug 53637 - NRVO not applied where there are two different variables involved 。
考虑以下 Person
的定义:
struct Person
{
Person() { std::cout << "construct, "; }
Person(const Person&) { std::cout << "copy\n"; }
Person(Person&&) { std::cout << "move\n"; }
};
和 3 个不同的函数来创建 Person
:
Person create1()
{
std::cout << "create1: ";
Person p1{};
return p1;
}
Person create2()
{
std::cout << "create2: ";
if constexpr (true)
{
Person p1{};
return p1;
}
else
{
Person p2{};
return p2;
}
}
Person create3()
{
std::cout << "create3: ";
if constexpr (true)
{
return Person{};
}
else
{
return Person{};
}
}
最后,我调用创建函数如下:
int main()
{
Person p1 = create1();
Person p2 = create2();
Person p3 = create3();
return 0;
}
输出为:
create1: construct
create2: construct, move
create3: construct
困扰我的是 create2
的输出。如果在create1
和create3
中没有调用move构造函数,为什么在create2
中调用呢?
我正在使用 GCC 12.0.0。
编辑:Clang 13.0.0 不调用移动构造函数。
我认为它是常量表达式中 NRVO 的一个实例 cppreference says:
In constant expression and constant initialization, return value optimization (RVO) is guaranteed, however, named return value optimization (NRVO) is forbidden:
这与CWG 2278有关。
编辑
我重新考虑了:-)它是从main()
调用的,所以不能常量初始化。通过实际常量初始化,gcc 和 clang 都不会执行 NRVO。在本例中 (see godbolt):
struct Person {
bool was_moved;
constexpr Person() : was_moved{false} { }
constexpr Person(const Person&) : was_moved{false} { }
constexpr Person(Person&&) : was_moved{true} { }
};
constexpr Person create() {
if (true) {
Person p1;
return p1;
}
else {
Person p2;
return p2;
}
}
constexpr Person p = create();
int main() {
return p.was_moved;
}
p.was_moved
是 1.
至于为什么 gcc 在 create2()
中不执行 NRVO,一个答案可能是它不是强制性的,根据之前的 cppreference link。另一个可能是 NRVO 的 gcc 实现非常“脆弱”。例如,当只涉及一个命名变量时它起作用:
Person create4() {
Person p{};
if (true) {
return p;
}
else {
return p;
}
}
但是当整个东西被放入子范围时停止工作:
Person create5() {
{
Person p{};
if (true) {
return p;
}
else {
return p;
}
}
}
从 2012 年开始就存在关于这两个问题的 gcc 错误:Bug 53637 - NRVO not applied where there are two different variables involved 。