【问题标题】:Order of execution in constructor initialization list构造函数初始化列表中的执行顺序
【发布时间】:2025-12-30 20:30:04
【问题描述】:

构造函数初始化列表中的执行顺序是否可确定?我知道类中的成员顺序是初始化这些成员的顺序,但如果我有这样的场景:

class X()
{
X_Implementation* impl_;
};  

and then providing that allocator is available:

X::X():impl_(Allocate(sizeof(X_Implementation)))//HERE I'M ALLOCATING <--1
,impl_(Construct<X_Implementation>(impl_))//AND HERE I'M CONSTRUCTING <--2
{
}

但为了使其可靠,此顺序必须从左到右。它是否由 GREAT BOOK OF std:: 保证?如果没有,我总是可以将第二行移到正文中。

【问题讨论】:

标签: c++ operator-precedence


【解决方案1】:

根据 ISO/IEC 14882:2003(E) 第 12.6.2 节:

初始化应按以下顺序进行:

  • 首先,并且仅对于如下所述的最派生类的构造函数,虚拟基类应 按照它们出现在有向无环图的深度优先从左到右遍历的顺序进行初始化 基类,其中“从左到右”是派生类中基类名称的出现顺序 类基说明符列表。
  • 然后,直接基类应按照它们出现在基说明符列表中的声明顺序进行初始化 (不管 mem-initializers 的顺序如何)。
  • 然后,非静态数据成员应按照它们在类定义中声明的顺序进行初始化 (同样不管 mem-initializers 的顺序如何)。
  • 最后,构造函数的主体被执行。

所以,按照这个顺序,你就会得到你的订单。同样根据标准,顺序是这样规定的,以便对象可以以完全相反的顺序未初始化。

【讨论】:

    【解决方案2】:

    C++ 标准确实保证了初始化列表的顺序(ISO C++ 标准 12.6.2/5):

    ...非静态数据成员应按照它们在类定义中声明的顺序进行初始化(同样与 mem-initializers 的顺序无关)。

    (有关详细信息,请参阅Wyatt Anderson's answer。)

    例子:

    class Foo
    {
    public:
        Foo();
    private:
        A a;
        B b;
        C c;
    };
    
    Foo::Foo() : b(), a(), c() {
        // a is initialized first, then b, then c - NOT b, a, then c!
    }
    

    但是,你不能两次初始化一个变量——你所拥有的不会编译。

    class X //() what's with the pair of parentheses you have in your code snippet?
    {
    public:
        X();
    private:
        X_Implementation* impl_;
    };
    
    X::X():
        impl_(Allocate(sizeof(X_Implementation))),
            // It is not allowed to initialize a data member twice!
        impl_(Construct<X_Implementation>(impl_)) {
    }
    

    相反,只需将额外的工作放入构造函数中:

    X::X() : impl_(Allocate(sizeof(X_Implementation))) {
        impl_ = Construct<X_Implementation>(impl_);
    }
    

    上述代码可能存在异常安全问题,但在不知道Allocate()Construct() 究竟是什么的情况下,我无法判断。我可以告诉你,如果你这样做的话,最好将分配和构造分离到它们自己的类中,使用 Resource Acquisition Is Initialization (RAII) 习惯用法:

    class XBase
    {
    protected:
        XBase() : impl_(Allocate(sizeof(X_Implementation))) { }
    
        ~XBase() {
            if( !impl_) { Deallocate(impl_); } // Or something like this
        }
    
        X_Implementation* impl_;
    };
    
    class X : private XBase // XBase is an implementation detail
    {
    public:
        X() {
            impl_ = Construct<X_Implementation>(impl_);
        }
    
        ~X() {
            Destruct<X_Implementation>(impl_); // Or something like this
        }
    };
    

    这样,如果Construct() 抛出异常,您将不会泄漏内存,因为将调用基类析构函数,这将释放impl_ 指向的内存。这很重要,因为如果异常没有被捕获并离开构造函数,则不会调用其匹配的析构函数。见Bjarne Stroustrup's paper on exception safety

    【讨论】:

      【解决方案3】:

      您的具体方案基于多次初始化同一个成员的想法。这在 C++ 中是完全非法的。您的代码将无法编译。所以,你问的问题并不存在。

      成员初始化的顺序是它们在类定义中的声明顺序。在涵盖与构造初始化器列表中的初始化顺序相关的所有内容的非继承上下文中。

      【讨论】: