📜  C++中初始化程序列表中的执行顺序(1)

📅  最后修改于: 2023-12-03 14:59:49.893000             🧑  作者: Mango

C++中初始化程序列表中的执行顺序

在C++中,初始化程序列表是一种用于在对象构造过程中进行初始化的方法。初始化程序列表位于构造函数的函数体之前,并用冒号(:)分隔。在初始化程序列表中,我们可以通过调用成员初始化列表来为对象的成员或基类初始化。

但是,初始化程序列表中的执行顺序并不总是很明确,这可能会导致一些难以调试的问题。因此,我们有必要深入了解C++中初始化程序列表的执行顺序。

1. 基本规则

C++标准规定,初始化程序列表中的执行顺序如下:

  1. 先执行基类的构造函数,按继承层次从上往下,逐个执行。
  2. 再按照声明顺序,对类中的成员进行初始化。

例如,对于以下代码:

class A {
public:
    A() { std::cout << "A constructor." << std::endl; }
};

class B {
public:
    B() { std::cout << "B constructor." << std::endl; }
};

class C : public A, public B {
public:
    C() : A(), B() { std::cout << "C constructor." << std::endl; }
};

int main() {
    C c;
    return 0;
}

输出结果为:

A constructor.
B constructor.
C constructor.

可以发现,C的构造函数中先调用了A和B的构造函数,并按照声明顺序分别进行初始化。也就是说,初始化程序列表中的执行顺序并不会影响这个基本规则。

2. 特殊情况

然而,有一些特殊情况可能需要我们注意初始化程序列表的执行顺序。

2.1 类型转换

如果初始化程序中涉及到类型转换,执行顺序就会变得更加复杂。因为在对类成员进行初始化时,可能需要对成员变量进行类型转换,这会涉及到构造函数的调用。

例如,对于以下代码:

class A {
public:
    A(int a) { std::cout << "A constructor. a = " << a << std::endl; }
};

class B {
public:
    B(float b) { std::cout << "B constructor. b = " << b << std::endl; }
};

class C {
public:
    C() : m_a(0), m_b(static_cast<float>(m_a)) {}
private:
    A m_a;
    B m_b;
};

int main() {
    C c;
    return 0;
}

输出结果为:

A constructor. a = 0
B constructor. b = 0

可以发现,此时的执行顺序是按照声明顺序的。也就是说,先调用了m_a的构造函数,然后再调用了m_b的构造函数。但是,注意到m_b的初始化中涉及到了一个类型转换,这会涉及到m_a的构造函数的调用。因此,在实际执行时,C的构造函数的代码执行顺序应该是这样的:

  1. 调用m_a的构造函数。
  2. 将m_a的值转换为float类型。
  3. 调用m_b的构造函数。
2.2 基类成员初始化

如果成员是一个基类,那么其构造函数的初始化顺序可能会依赖于派生类的构造函数。因此,我们需要确保派生类的构造函数在基类构造函数之前调用。

例如,对于以下代码:

class A {
public:
    A(int a) { std::cout << "A constructor. a = " << a << std::endl; }
};

class B {
public:
    B() { std::cout << "B constructor." << std::endl; }
};

class C : public A {
public:
    C() : A(0), m_b() { std::cout << "C constructor." << std::endl; }
private:
    B m_b;
};

int main() {
    C c;
    return 0;
}

输出结果为:

A constructor. a = 0
B constructor.
C constructor.

可以发现,在构造C时,必须先调用A的构造函数,然后才能调用B的构造函数。因此,为了确保执行顺序正确,我们在初始化程序列表中必须先调用A的构造函数,再调用B的构造函数。

3. 总结

在C++中,初始化程序列表的执行顺序有一些规则和特殊情况。我们需要确保遵循基本规则,并注意特殊情况,以确保程序的正确性。在写代码时,最好能够遵循以下几点:

  1. 优先使用成员初始化列表来初始化类成员和基类,以避免难以调试的错误。
  2. 遵循基本规则,先调用基类构造函数,再按照声明顺序初始化成员。
  3. 特别是在涉及到类型转换和基类成员初始化时,需要注意执行顺序的问题,确保程序的正确性。