成员初始化列表真的更有效吗?
Are member-initialization lists really more efficient?
我同意这样的共识,即通常最好在成员初始化列表而不是构造函数的主体中初始化 C++ 数据成员,但我对 this explanation
持怀疑态度
The other (inefficient) way to build constructors is via assignment, such as: Fred::Fred() { x_ = whatever; }.
In this case the expression whatever causes a separate, temporary object to be created, and this temporary object is passed into the x_ object’s assignment operator. Then that temporary object is destructed at the ;
. That’s inefficient.
这真的正确吗?我本以为编译器会省略默认构造的临时对象,该对象会立即被主体中的赋值替换。我不知道为什么我会这么期待,但在阅读了上述声明后,我想我多年来一直在默默地假设它。
成员初始化列表实际上更有效吗?如果有,是不是这个原因?
用Alexandrescu & Sutter(第9项)的话不要过早悲观
Avoiding premature optimization does not imply gratuitously hurting
efficiency. By premature pessimization we mean writing such gratuitous
potential inefficiencies as:
• Defining pass-by-value parameters when pass-by-reference is
appropriate. (See Item25.)
• Using postfix + + when the prefix version is just as good. (See
Item 28.)
• Using assignment inside constructors instead of the initializer
list. (See Item 48.)
每当您在构造函数中编写赋值时,您的代码审阅者都会保持警惕:发生了什么特别的事情吗?他真的想要一些特殊的 two-stage 初始化吗(因为无论如何都会生成成员的隐式默认构造!)。不要无缘无故地让代码的读者感到惊讶。
请注意,Alexandrescu 和 Sutter 在第 48 项中继续讨论 潜在的 效率低下,但不要在任何地方声称存在 实际的 实际优化代码效率低下。这也是题外话,它是关于表达意图 和避免效率低下的风险。
Are member initialization lists actually more efficient? If so, is it for this reason?
一般是的。通过成员初始化,您可以将值直接传递给构造函数,否则将创建一个 default-constructed 对象,然后调用赋值运算符。请注意,这与您提供的报价中提到的 "temporary" 无关,这是关于字段本身的。
你可以看到直播here
class Verbose {
public:
Verbose() { std::cout << "Verbose::Verbose()" << std::endl; }
Verbose( int ) { std::cout << "Verbose::Verbose(int)" << std::endl; }
Verbose &operator=( int ) { std::cout << "Verbose::operator=(int)" << std::endl; }
};
class A {
public:
A() : v( 0 ) {}
A(int) { v = 0; }
private:
Verbose v;
};
int main() {
std::cout << "case 1 --------------------" << std::endl;
A a1;
std::cout << "case 2 --------------------" << std::endl;
A a2( 0 );
// your code goes here
return 0;
}
输出:
case 1 --------------------
Verbose::Verbose(int)
case 2 --------------------
Verbose::Verbose()
Verbose::operator=(int)
使用成员初始化列表,
#include <string>
struct Fred {
Fred() : x_("hello") { }
std::string x_;
};
int main() {
Fred fred;
}
Clang 3.9.1 和 gcc 6.3 使用 -O3 -fno-exceptions
(Compiler Explorer) 生成以下内容:
main: # @main
xor eax, eax
ret
如果我们改为在正文中进行赋值:
#include <string>
struct Fred {
Fred() { x_ = "hello"; }
std::string x_;
};
int main() {
Fred fred;
}
两者都会生成更多的代码,例如Clang 3.9.1 输出这个:
main: # @main
push rbx
sub rsp, 32
lea rbx, [rsp + 16]
mov qword ptr [rsp], rbx
mov qword ptr [rsp + 8], 0
mov byte ptr [rsp + 16], 0
lea rdi, [rsp]
xor esi, esi
xor edx, edx
mov ecx, .L.str
mov r8d, 5
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long)
mov rdi, qword ptr [rsp]
cmp rdi, rbx
je .LBB0_2
call operator delete(void*)
.LBB0_2:
xor eax, eax
add rsp, 32
pop rbx
ret
.L.str:
.asciz "hello"
所以看起来成员 init 列表确实更有效,至少在某些情况下,即使使用现代编译器也是如此。
我同意这样的共识,即通常最好在成员初始化列表而不是构造函数的主体中初始化 C++ 数据成员,但我对 this explanation
持怀疑态度The other (inefficient) way to build constructors is via assignment, such as:
Fred::Fred() { x_ = whatever; }.
In this case the expression whatever causes a separate, temporary object to be created, and this temporary object is passed into the x_ object’s assignment operator. Then that temporary object is destructed at the;
. That’s inefficient.
这真的正确吗?我本以为编译器会省略默认构造的临时对象,该对象会立即被主体中的赋值替换。我不知道为什么我会这么期待,但在阅读了上述声明后,我想我多年来一直在默默地假设它。
成员初始化列表实际上更有效吗?如果有,是不是这个原因?
用Alexandrescu & Sutter(第9项)的话不要过早悲观
Avoiding premature optimization does not imply gratuitously hurting efficiency. By premature pessimization we mean writing such gratuitous potential inefficiencies as:
• Defining pass-by-value parameters when pass-by-reference is appropriate. (See Item25.)
• Using postfix + + when the prefix version is just as good. (See Item 28.)
• Using assignment inside constructors instead of the initializer list. (See Item 48.)
每当您在构造函数中编写赋值时,您的代码审阅者都会保持警惕:发生了什么特别的事情吗?他真的想要一些特殊的 two-stage 初始化吗(因为无论如何都会生成成员的隐式默认构造!)。不要无缘无故地让代码的读者感到惊讶。
请注意,Alexandrescu 和 Sutter 在第 48 项中继续讨论 潜在的 效率低下,但不要在任何地方声称存在 实际的 实际优化代码效率低下。这也是题外话,它是关于表达意图 和避免效率低下的风险。
Are member initialization lists actually more efficient? If so, is it for this reason?
一般是的。通过成员初始化,您可以将值直接传递给构造函数,否则将创建一个 default-constructed 对象,然后调用赋值运算符。请注意,这与您提供的报价中提到的 "temporary" 无关,这是关于字段本身的。
你可以看到直播here
class Verbose {
public:
Verbose() { std::cout << "Verbose::Verbose()" << std::endl; }
Verbose( int ) { std::cout << "Verbose::Verbose(int)" << std::endl; }
Verbose &operator=( int ) { std::cout << "Verbose::operator=(int)" << std::endl; }
};
class A {
public:
A() : v( 0 ) {}
A(int) { v = 0; }
private:
Verbose v;
};
int main() {
std::cout << "case 1 --------------------" << std::endl;
A a1;
std::cout << "case 2 --------------------" << std::endl;
A a2( 0 );
// your code goes here
return 0;
}
输出:
case 1 --------------------
Verbose::Verbose(int)
case 2 --------------------
Verbose::Verbose()
Verbose::operator=(int)
使用成员初始化列表,
#include <string>
struct Fred {
Fred() : x_("hello") { }
std::string x_;
};
int main() {
Fred fred;
}
Clang 3.9.1 和 gcc 6.3 使用 -O3 -fno-exceptions
(Compiler Explorer) 生成以下内容:
main: # @main
xor eax, eax
ret
如果我们改为在正文中进行赋值:
#include <string>
struct Fred {
Fred() { x_ = "hello"; }
std::string x_;
};
int main() {
Fred fred;
}
两者都会生成更多的代码,例如Clang 3.9.1 输出这个:
main: # @main
push rbx
sub rsp, 32
lea rbx, [rsp + 16]
mov qword ptr [rsp], rbx
mov qword ptr [rsp + 8], 0
mov byte ptr [rsp + 16], 0
lea rdi, [rsp]
xor esi, esi
xor edx, edx
mov ecx, .L.str
mov r8d, 5
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace(unsigned long, unsigned long, char const*, unsigned long)
mov rdi, qword ptr [rsp]
cmp rdi, rbx
je .LBB0_2
call operator delete(void*)
.LBB0_2:
xor eax, eax
add rsp, 32
pop rbx
ret
.L.str:
.asciz "hello"
所以看起来成员 init 列表确实更有效,至少在某些情况下,即使使用现代编译器也是如此。