为什么带大括号的复制初始化会省略 copy/move 构造?
Why does copy initialisation with braces elide copy/move construction?
这是一个 C++ 14 程序,用于比较 direct initialisation (no =
) with copy initialisation (=
):*
#include <iostream>
struct A {
A(int) { std::cout << "A(int)" << std::endl; }
A(A&) { std::cout << "A(A&)" << std::endl; }
A(A&&) { std::cout << "A(A&&)" << std::endl; }
};
int main() {
A a(1); // direct initialisation
A b{1}; // direct initialisation
A c = 1; // copy initialisation
A d = (1); // copy initialisation
A e = {1}; // copy initialisation
}
编译程序禁用copy elision和运行它:
$ clang++ -std=c++14 -fno-elide-constructors main.cpp && ./a.out
产生以下输出:
A(int)
A(int)
A(int)
A(A&&)
A(int)
A(A&&)
A(int)
为什么用大括号 (A e = {1};
) 复制初始化省略 copy/move 构造(即使禁用复制省略)?
* 此比较背后的动机是了解函数 return 语句 (return expression
) 自 C++ 11 起如何工作。当 return 按值计算时,可以使用函数 return 值的直接初始化或复制初始化。后者比像这里这样的变量的复制初始化更复杂,因为从 expression
表示的对象初始化函数 return 值涉及 trying to call the move constructor of the function return type first (even if expression
is an lvalue) before falling back on its copy constructor. And since C++ 17, that copy/move construction is guaranteed to be elided if expression
is a prvalue (mandatory return value optimisation), while it might be elided if expression
is a glvalue (optional named return value optimisation).
copy initialisation with braces
没有这样的事情。如果您使用大括号初始化列表来初始化一个对象,那么您正在执行某种形式的 列表初始化 。有两种形式:复制列表初始化和直接列表初始化。在 C++14 中,这些与复制初始化和直接初始化无关(从技术上讲,直接列表初始化是 grammatical form of direct-initialization, but since list-initialization bypasses everything that direct-initialization would have done,更容易说直接列表初始化是它自己的野兽)。
列表初始化作为一个概念初始化一个对象。使用 Typename t{}
是直接列表初始化,而 Typename t = {}
是复制列表初始化。但是无论涉及哪种形式,都没有创建临时;列表初始化初始化有问题的对象。您的示例中唯一的对象是 e
,因此这是被初始化的对象。
根据C++14 rules for list-initialization, e
gets initialized by calling a constructor,给它传递一个值1
,这是braced-init-list中唯一的值。
这是一个 C++ 14 程序,用于比较 direct initialisation (no =
) with copy initialisation (=
):*
#include <iostream>
struct A {
A(int) { std::cout << "A(int)" << std::endl; }
A(A&) { std::cout << "A(A&)" << std::endl; }
A(A&&) { std::cout << "A(A&&)" << std::endl; }
};
int main() {
A a(1); // direct initialisation
A b{1}; // direct initialisation
A c = 1; // copy initialisation
A d = (1); // copy initialisation
A e = {1}; // copy initialisation
}
编译程序禁用copy elision和运行它:
$ clang++ -std=c++14 -fno-elide-constructors main.cpp && ./a.out
产生以下输出:
A(int)
A(int)
A(int)
A(A&&)
A(int)
A(A&&)
A(int)
为什么用大括号 (A e = {1};
) 复制初始化省略 copy/move 构造(即使禁用复制省略)?
* 此比较背后的动机是了解函数 return 语句 (return expression
) 自 C++ 11 起如何工作。当 return 按值计算时,可以使用函数 return 值的直接初始化或复制初始化。后者比像这里这样的变量的复制初始化更复杂,因为从 expression
表示的对象初始化函数 return 值涉及 trying to call the move constructor of the function return type first (even if expression
is an lvalue) before falling back on its copy constructor. And since C++ 17, that copy/move construction is guaranteed to be elided if expression
is a prvalue (mandatory return value optimisation), while it might be elided if expression
is a glvalue (optional named return value optimisation).
copy initialisation with braces
没有这样的事情。如果您使用大括号初始化列表来初始化一个对象,那么您正在执行某种形式的 列表初始化 。有两种形式:复制列表初始化和直接列表初始化。在 C++14 中,这些与复制初始化和直接初始化无关(从技术上讲,直接列表初始化是 grammatical form of direct-initialization, but since list-initialization bypasses everything that direct-initialization would have done,更容易说直接列表初始化是它自己的野兽)。
列表初始化作为一个概念初始化一个对象。使用 Typename t{}
是直接列表初始化,而 Typename t = {}
是复制列表初始化。但是无论涉及哪种形式,都没有创建临时;列表初始化初始化有问题的对象。您的示例中唯一的对象是 e
,因此这是被初始化的对象。
根据C++14 rules for list-initialization, e
gets initialized by calling a constructor,给它传递一个值1
,这是braced-init-list中唯一的值。