编译器生成的默认构造函数如何比只初始化成员什么都不做的自写构造函数更有效? [英] How can a compiler generated default constructor be more efficient than a self-written one that does nothing but initialize members?

查看:74
本文介绍了编译器生成的默认构造函数如何比只初始化成员什么都不做的自写构造函数更有效?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此答案触发:我在

C.45:不要定义仅初始化数据的默认构造函数成员;改用类内成员初始化程序

C.45: Don’t define a default constructor that only initializes data members; use in-class member initializers instead

给出的理由是

原因

使用类内成员初始化程序可使编译器生成为您服务.编译器生成的函数可以更多

Using in-class member initializers lets the compiler generate the function for you. The compiler-generated function can be more efficient.

请注意,这是专门针对默认的构造函数,该初始化函数除了初始化成员外不执行任何操作,并且准则建议不应编写这样的构造函数.

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 defaulted 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 like static 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 a constexpr context -- as long as the constexprness 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, defaulted 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 defaulting:

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 not noexcept

将此与以下内容进行对比:

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{} is constexpr, because std::unique_ptr's std::nullptr_t constructor is constexpr (even though std::unique_ptr cannot be used in a full constant expression)
  • Foo{} is a noexcept expression

您可以将生成的程序集与此 实时示例 进行比较.请注意, default 情况不需要任何指令即可初始化 foo .相反,它只是通过编译器指令将这些值分配为常量(即使该值不是常量).

You can compare the generated assembly with this Live Example. 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 defaulting 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 defaulted 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.

这篇关于编译器生成的默认构造函数如何比只初始化成员什么都不做的自写构造函数更有效?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆