问题描述
给出的理由是
使用类内成员初始化程序可使编译器生成为您服务.编译器生成的函数可以更多
Using in-class member initializers lets the compiler generate thefunction for you. The compiler-generated function can be moreefficient.
请注意,这是专门针对默认的构造函数,该初始化函数除了初始化成员外不执行任何操作,并且准则建议不应编写这样的构造函数.
Note that this is specifically about a default constructor that does nothing but initialize the members and the guideline suggests that one should not write such a constructor.
不好"的例子是:
Example, bad
class X1 { // BAD: doesn't use member initializers
string s;
int i;
public:
X1() :s{"default"}, i{1} { }
// ...
};
好"字样示例使用类内成员初始化程序,并且没有用户声明的构造函数:
The "good" example is using in-class member initializers and no user declared constructor:
Example
class X2 {
string s = "default";
int i = 1;
public:
// use compiler-generated default constructor
// ...
};
在该特定示例(或任何其他示例)中,编译器生成的构造函数比用户提供的构造函数更有效率吗?
What can the compiler generated constructor do more efficient than the user-provided one in that particular example (or in any other example)?
初始化程序列表是否没有提供与类初始化程序相同的优化机会?
Is the initializer list not giving the same opportunities for optimization as in-class initializers?
推荐答案
简短回答
默认
构造函数应具有与等效的初始化程序构造函数相同的生成程序集,前提是作者包括正确的 constexpr
和 noexcept 代码>状态
.
Short Answer
A default
ed constructor should have the same generated assembly as the equivalent initializer constructor provided that the author includes the correct constexpr
and noexcept
statuses.
我怀疑可以更有效率"指的是这样一个事实,即与可能错过机会的开发人员编写的等效代码(例如, inline
, constexpr
和没有例外
.
I suspect the "can be more efficient" is referring to the fact that, in general, it will generate more optimal code than the equivalent developer-authored one that misses opportunities such as inline
, constexpr
, and noexcept
.
默认构造函数执行的一个重要功能是,它们解释并推断 constexpr
和 noexcept
这是许多C ++开发人员未指定或可能未正确指定的内容.由于核心指南既针对新老C ++开发人员,也可能是优化"的原因.被提及.
This is something that many C++ developers do not specify, or may not specify correctly. Since Core Guidelines targets both new and old C++ developers, this is likely why the "optimization" is being mentioned.
constexpr
和 noexcept
状态可能以不同方式影响代码生成:
The constexpr
and noexcept
statuses may affect code generation in different ways:
-
constexpr
构造函数确保从常量表达式产生的值中调用构造函数也会产生一个常量表达式.这可以允许诸如static
值之类的非恒定值实际上不需要构造函数调用(例如,无需静态初始化开销或锁定).注意:这适用于本身无法在constexpr
上下文中存在的类型-只要其类型的constexpr
ness构造函数格式正确.
constexpr
constructors ensure that invocations of a constructor from values yielded from constant expressions will also yield a constant expression. This can allow things likestatic
values that are not constant to not actually require a constructor invocation (e.g. no static initialize overhead or locking required). Note: this works for types that are not, themselves, able to exist in aconstexpr
context -- as long as theconstexpr
ness of the constructor is well-formed.
noexcept
可能会生成更好的消费代码汇编,因为编译器可能会假定不会发生任何异常(因此不需要堆栈展开代码).此外,诸如模板之类的实用程序会检查 std :: is_nothrow_constructible ...
可能会生成更多的最佳代码路径.
noexcept
may generate better assembly of consuming code since the compiler may assume that no exceptions may occur (and thus no stack-unwinding code is necessary). Additionally, utilities such as templates that check for std::is_nothrow_constructible...
may generate more optimal code paths.
除此之外,在类主体中定义的 default
ed构造函数还使调用者可以看到它们的定义-从而可以更好地进行内联(否则,这可能是一个错失良机的机会)进行优化).
Outside of that, default
ed constructors defined in the class-body also make their definitions visible to the caller -- which allows for better inlining (which, again, may otherwise be a missed-opportunity for an optimization).
核心准则"中的示例并未很好地说明这些优化.但是,请考虑以下示例,该示例说明了一个可以从 default
ing中受益的现实示例:
The examples in the Core Guidelines don't demonstrate these optimizations very well. However, consider the following example, which illustrates a realistic example that can benefit from default
ing:
class Foo {
int a;
std::unique_ptr<int> b;
public:
Foo() : a{42}, b{nullptr}{}
};
在此示例中,以下是正确的:
In this example, the following are true:
-
Foo {}
的构造不是常数 -
Foo {}
的构造不是noexcept
- A construction of
Foo{}
is not a constant expression - Construction of
Foo{}
is notnoexcept
将此与以下内容进行对比:
Contrast this to:
class Foo {
int a = 42;
std::unique_ptr<int> b = nullptr;
public:
Foo() = default;
};
在表面上,这似乎是相同的.但是突然之间,以下内容发生了变化:
On the surface, this appears to be the same. But suddenly, the following now changes:
-
Foo {}
是constexpr
,因为std :: unique_ptr
的std :: nullptr_t
构造函数是constexpr
(即使std:: unique_ptr
不能在完整的常量表达式中使用) -
Foo {}
是noexcept
表达式
Foo{}
isconstexpr
, becausestd::unique_ptr
'sstd::nullptr_t
constructor isconstexpr
(even thoughstd::unique_ptr
cannot be used in a full constant expression)Foo{}
is anoexcept
expression
您可以将生成的程序集与此进行比较.请注意, default
情况不需要任何指令即可初始化 foo
.相反,它只是通过编译器指令将这些值分配为常量(即使该值不是常量).
You can compare the generated assembly with this . Note that the default
case does not require any instructions to initialize foo
; instead it simply assigns the values as constants through compiler directive (even though the value is not constant).
当然,也可以这样写:
class Foo {
int a;
std::unique_ptr<int> b;
public:
constexpr Foo() noexcept :a{42}, b{nullptr};
};
但是,这需要先验知识, Foo
可以 同时是 constexpr
和 noexcept
.弄错了会导致问题.更糟糕的是,随着代码随着时间的推移而发展, constexpr
/ noexcept
状态可能会变得不正确-这是 default
构造函数会出现的情况抓到了.
However, this requires prior knowledge that Foo
is able to be both constexpr
and noexcept
. Getting this wrong can lead to problems. Worse yet, as code evolves over time, the constexpr
/noexcept
state may become incorrect -- and this is something that default
ing the constructor would have caught.
使用 default
的另一个好处是,随着代码的发展,它可能会 add constexpr
/ noexcept
在可能的地方-例如标准库添加了更多的 constexpr
支持时.最后一点是,否则每次更改作者的代码时,都将是手动过程.
Using default
also has the added benefit that, as code evolves, it may add constexpr
/noexcept
where it becomes possible -- such as when the standard library adds more constexpr
support. This last point is something that would otherwise be a manual process every time code changes for the author.
如果您不使用类内成员初始化器,那么最后一个值得一提的是:代码中没有办法实现琐碎的事,除非它是由编译器生成的(例如通过 default
ed构造函数).
If you take away the use of in-class member initializers, then there is one last worthwhile point mentioning: there is no way in code to achieve triviality unless it gets compiler-generated (such as through default
ed constructors).
class Bar {
int a;
public:
Bar() = default; // Bar{} is trivial!
};
琐碎为潜在的优化提供了一个完全不同的方向,因为琐碎的默认构造函数不需要对编译器进行任何操作.这样,如果编译器发现对象后来被覆盖,则可以完全省略任何 Bar {}
.
Triviality offers a whole different direction on potential optimizations, since a trivial default-constructor requires no action on the compiler. This allows the compiler to omit any Bar{}
entirely if it sees that the object is later overwritten.
这篇关于编译器生成的默认构造函数如何比只初始化成员什么都不做的自写构造函数更有效?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!