C++中构造函数的初始化顺序说明

 更新时间:2025年07月29日 16:52:08   作者:Littlewith  
C++构造函数调用遵循基类→成员对象→派生类顺序,虚函数在构造中静态绑定,完成后动态绑定,拷贝构造在初始化、赋值及容器操作时调用,RVO优化可能避免拷贝,虚函数重写自子类起生效,不追溯基类

关键规则

  • 如果派生类有基类(单继承或多继承),基类的构造函数会首先被调用。

对基类进行处理

  • 多继承时,按照派生类继承列表中声明的顺序(从左到右)依次调用基类的构造函数。
  • 如果有虚继承,虚基类的构造函数优先于非虚基类调用,且只调用一次
  • 虚基类只在最远派生类中进行处理,并且只有最远派生类调用,其他虚继承的派生类调用被忽略,并且只执行一次

对成员对象进行处理

  • 在基类构造函数调用完成后,派生类中声明的成员对象的构造函数会被调用。
  • 成员对象的构造顺序遵循它们在类中声明的顺序(而不是初始化列表中的顺序)。
  • 如果成员对象有自己的构造函数,C++ 会根据初始化列表(如果提供)或默认构造函数来调用。

派生类自己的构造函数

  • 最后,派生类自己的构造函数体被执行。

特殊情况

如果父类构造函数含有虚函数调用

  • 在父类的构造函数中调用虚函数,还是会执行父类的构造函数,不会跑到子类中去,即使有vitual,因为此时父类都还没有构造完成,子类也就还没有构造。
#include <iostream>
using namespace std;
 
class A{
  public:
    A ():m_iVal(0){test();}//这里的test会调用父类的virtual void func()
    virtual void func() { std::cout<<m_iVal<<' ';}
    void test(){func();}
  public:
      int m_iVal;
};
class B : public A{
  public:
    B(){test();}//这里的test会调用父类的test()进而通过指针匹配
    virtual void func(){
      ++m_iVal;
      std::cout << m_iVal << ' ';
      }
};
int main(int argc ,char* argv[]){
  A*p = new B;
  p->test();
  return 0;
}

输出结果

0 1 2

虚函数调用规则

  • 构造函数中:绑定到当前构造的类版本
  • 构造完成后:动态绑定到实际对象类型

执行流程

  • A::A() → test() → A::func() → 输出0(vtable指向A)。
  • B::B() → test() → B::func() → m_iVal++,输出1(vtable指向B)。
  • p->test() → B::func() → m_iVal++,输出2。

vtable切换

  • 基类构造:指向A。
  • 基类完成后,进入B::B()前:指向B。

输出

0 1 2。

核心结论

  • 构造函数中虚函数调用取决于当前类类型,非最终类型。
  • vtable在基类构造后、派生类构造函数体前更新。
  • 注意:在执行构造函数体之前,可以认为构造函数**已经完全完成了相应对象的初始化工作,**在C++的实现中,虚函数表的切换发生在进入派生类构造函数体之前,而不是等到整个构造函数结束。

关于拷贝构造函数的初始化问题

  • 在初始化时如果对象不存在,且没有声明explict禁用赋值操作
  • 编译器默认调用拷贝构造函数
#include<iostream>
using namespace std;

class MyClass {
public:
    MyClass(int i = 0) { // 构造函数
        cout << i;
    }
    MyClass(const MyClass &x) { // 拷贝构造函数
        cout << 2;
    }
    MyClass &operator=(const MyClass &x) { // 赋值运算符
        cout << 3;
        return *this;
    }
    ~MyClass() { // 析构函数
        cout << 4;
    }
};

int main() {
    MyClass obj1(1), obj2(2); // 创建 obj1 和 obj2
    MyClass obj3 = obj1;      // 创建 obj3 并初始化
    return 0;
}
MyClass obj3 = obj1;

由于obj3为被创建,那么调用拷贝构造函数,称为复制初始化

MyClass obj3; // 先默认构造
obj3 = obj1; // 再赋值

这时会导致调用赋值运算符

拷贝构造函数的什么时候被调用呢?

场景示例代码说明
对象初始化MyClass b = a;或者MyClass b(a);用已有对象初始化新对象
按值传递参数void func(MyClass x);函数参数创建副本
按值返回对象MyClass func() { … }返回局部对象(可能被优化,现如今的C++编译器普遍采用了RVO返回值优化导致返回时候不会进行拷贝)
容器操作vec.push_back(a);插入对象到容器
显式调用new MyClass(a);动态分配时拷贝

关于子类和父类的虚函数问题

如果父类函数不是 virtual,子类将其声明为 virtual:

  • 对基类无影响,基类调用仍是静态绑定。
  • 从子类开始,函数成为虚函数,后续派生类可以实现多态,(即便是后续没有加virtual关键字也是多态)
#include <iostream>
class Base {
public:
    void foo() { // 非虚函数
        std::cout << "Base::foo()" << std::endl;
    }
};

class Derived : public Base {
public:
    virtual void foo() { // 子类声明为虚函数
        std::cout << "Derived::foo()" << std::endl;
    }
};

class GrandDerived : public Derived {
public:
    void foo() { // 重写 Derived 中的虚函数
        std::cout << "GrandDerived::foo()" << std::endl;
    }
};

int main() {
    Base* b1 = new Derived();
    b1->foo(); // 输出 "Base::foo()"

    Derived* d1 = new Derived();
    d1->foo(); // 输出 "Derived::foo()"

    Base* b2 = new GrandDerived();
    b2->foo(); // 输出 "Base::foo()"

    Derived* d2 = new GrandDerived();
    d2->foo(); // 输出 "GrandDerived::foo()"
    delete b1; delete d1; delete b2; delete d2;
    return 0;
}

父类函数非虚,没有多态

  • 因为 Base::foo() 不是虚函数,通过 Base* 指针调用 foo() 时**,总是执行 Base::foo()**,不会发生运行时多态。
  • 即使 Derived::foo() 被声明为 virtual,它对 Base 的函数没有影响,因为多态性需要从基类开始启用。

子类声明为虚,影响后续继承

  • 在 Derived 中将 foo() 声明为 virtual,意味着从 Derived 开始,这个函数变成了虚函数。
  • 后续的派生类(如 GrandDerived)可以重写 Derived::foo(),并通过 Derived* 或 Derived& 调用时实现多态。
  • 但这种多态仅限于 Derived 及其子类,不追溯到 Base。

隐藏而非重写

  • Derived::foo() 只是隐藏了 Base::foo(),而不是重写它。
  • 当通过 Base* 调用时,调用的仍然是 Base::foo(),因为 Base::foo() 不是虚函数。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • C++中BitBlt的使用方法详解

    C++中BitBlt的使用方法详解

    这篇文章主要介绍了C++中BitBlt的使用方法详解的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下
    2017-09-09
  • C++特殊成员详解

    C++特殊成员详解

    这篇文章主要为大家介绍了C++特殊成员,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助,希望能够给你带来帮助
    2021-11-11
  • QT使用QChart绘制面积图

    QT使用QChart绘制面积图

    在Qt中使用QChart类可以快速绘制一个图表出来,比如折线图、饼图、柱状图等,本文就来为大家介绍一下如何利用QChart绘制简单的面积图吧
    2024-11-11
  • C++超详细讲解友元的使用

    C++超详细讲解友元的使用

    采用类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,依此提供类与外界间的通信接口。但是,有时需要定义一些函数,这些函数不是类的一部分,但又需要频繁地访问类的数据成员,这时可以将这些函数定义为该类的友元函数
    2022-04-04
  • C++ 中的 JSON 序列化和反序列化及结构体与枚举类型的处理方法

    C++ 中的 JSON 序列化和反序列化及结构体与枚举类型的处理方法

    在 C++ 编程中,处理 JSON 数据是一项常见任务,特别是在需要与其他系统或前端进行数据交换时,本文将详细介绍如何使用 nlohmann::json 库对结构体和枚举类型进行序列化和反序列化,感兴趣的朋友一起看看吧
    2024-11-11
  • C++选择文件夹代码的封装

    C++选择文件夹代码的封装

    这篇文章主要介绍了C++选择文件夹代码的封装,实例展示了将选择文件夹功能代码封装为一个类并对其进行实例化调用的过程,对于学习C++程序设计有不错的参考价值,需要的朋友可以参考下
    2014-10-10
  • C++数据结构AVL树全面分析

    C++数据结构AVL树全面分析

    今天的这一篇博客,我要跟大家介绍一颗树——AVL树,它也是一颗二叉搜索树,它就是在二叉搜索树中加了一个平衡因子的概念在里面,下面我就来和大家聊一聊这棵树是个怎么样的树
    2021-10-10
  • 详谈C语言指针

    详谈C语言指针

    这篇文章主要介绍了C语言的指针,介绍了其相关概念,然后分享了几种用法,具有一定参考价值。需要的朋友可以了解下
    2021-10-10
  • Qt实现模糊匹配功能的实例详解

    Qt实现模糊匹配功能的实例详解

    对于浏览器的使用,我想大家一定不会陌生吧,输入要搜索的内容时,会出现相应的匹配信息。本文就来用Qt实现模糊匹配功能,感兴趣的可以了解一下
    2022-10-10
  • C语言 数据结构之连续存储数组的算法

    C语言 数据结构之连续存储数组的算法

    这篇文章主要介绍了C语言 数据结构之连续存储数组的算法的相关资料,需要的朋友可以参考下
    2017-01-01

最新评论