对于 C 对象而言我们认为对象 内存 语义不变量。内存仅仅是电子与硅晶体中状态未知的比特位。语义这段内存代表什么含义是int、是char还是float以及它必须满足的条件“不变量”Invariant。构造函数Constructor的本质就是将 “原始、混沌” 的内存强制转换为 “持有特定语义的、合法的对象” 的原子操作过程。核心逻辑在 C 语言中创建一个struct通常分为两步分配内存malloc或栈上声明赋值init函数或手动赋值问题在于如果在第 1 步和第 2 步之间使用该对象就会导致灾难未定义行为。或者如果使用者忘记了第 2 步系统就会处于 “非法状态”。C 引入构造函数就是为了保证如果一个对象存在那么它一定是合法的。构造函数保证了初始化Initialization与定义Defination的不可分割性。构造函数的执行流当你写下T object(args);时编译器实际执行了以下步骤分配内存在栈或堆上找到一块足够容纳sizeof(T)的空间。此时内存里的数据是随机的Garbage。执行初始化列表Initialization List这是真正的初始化时刻。执行函数体Function Body这实际上是后续的计算或赋值操作而非初始化。为什么首选初始化列表因为 C 规定成员变量在进入构造函数体{}之前必须完成构建。Class() : member(value) {} // 直接在内存位置上构造 member使用初始化列表的成本仅为 1 次构造。Class() { member value; }过程调用member的默认构造函数无参。调用member的赋值运算符operator。在这个过程中的成本为1 次构造 1 次赋值还可能设计旧内存释放和新内存申请。初始化列表不仅是效率优化对于const成员或reference引用成员它是唯一的初始化方式因为它们创建后不可修改不可赋值。构造函数的分类根据对象资源管理的不同需求构造函数演化出了四种主要形态。我们将用资源所有权的视角来区分它们。默认构造函数Default Constructor语义无中生有形式T()视角当对象被创建但外界未提供任何信息时对象应处于什么状态通常是 “空状态” 或 “零状态”注意如果类中包含原始指针编译器生成的默认构造函数不会置空指针由于 C 的遗留包袱这会导致悬垂指针。因此现代 C 提倡显式定义或使用成员默认初始化int* p nullptr;参数化构造函数Parameterized Constructor语义根据蓝图定制形式T(args...)视角将外部数据约束映射到内部不变量。例如创建 “圆” 对象参数是半径。构造函数必须检查radius 0这就是维护 “不变量”拷贝构造函数Copy Constructor语义复制细胞分裂、克隆形式T(const T other)视角如果对象时值语义如整数、坐标直接按位拷贝Shallow Copy如果对象持有资源如堆内存指针、文件句柄必须进行深拷贝Deep Copy本质矛盾如果只复制指针两个对象指向同一块内存析构时会发生 “Double Free” 错误。因此拷贝构造函数必须重新分配资源。移动构造函数Move Constructor移动构造函数是 C11 提出的革命性进步。语义所有权转移器官移植形式T(T other)视角在 C98 中如果要将一个临时对象即将销毁放入容器比如先复制再销毁。这极度浪费性能如复制一个巨大的std::vector移动构造函数利用右值引用识别出other是一个即将消亡的对象。它偷走other的资源指针指向新主旧指针置空而非复制数据代价极低仅是指针赋值关键机制与陷阱explicit关键字拒绝隐式转换C 默认允许单参数构造函数进行隐式类型转换。struct Buffer { Buffer(int size) { ... } }; void func(Buffer b); func(42) // 编译器偷偷执行了 Buffer(42)可能并不是你想要的从安全角度Safety First出发隐式类型转换破坏了强类型系统。标记explicit禁止这种 “自作聪明” 的行为强制显式调用。委托构造Delegating Constructors允许一个构造函数调用同类的另一个构造函数。这是为了准许DRYDont Repeat Yourself原则防止初始化逻辑碎片化。构造与虚函数永远不要在构造函数中调用虚函数。原理在基类构造期间派生类的部分尚未初始化。为了安全C 此时将对象视为基类类型。虚函数表vtalbe指针指向基类表多态失效。RAII 与构造函数将上述所有内容串联起来的概念就是 RAIIResource Acquisition Is Initialization这是 C 的灵魂。资源获取即初始化资源的生命周期严格绑定对象的生命周期构造函数资源的获取点锁住互斥量、打开文件、分配内存析构函数资源的释放点解锁、关闭、释放C 的构造函数不仅仅是用来 “赋值” 的函数它是类型系统安全性的守门人是资源管理自动化的起点。掌握构造函数不仅仅是记住语法而是要时刻思考这个对象诞生的一瞬间我如何保证它拥有了所需的资源且处于绝对合法的状态相关关键字控制编译器行为C 编译器通常会 “自作聪明” 地为你生成默认构造、拷贝构造等。以下关键字则可以用于精确控制这种自动行为。 default语义出厂设置当你手写了一个参数化构造函数T(int a)后编译器认为你是一个有主见的人于是不再自动生成无参的默认构造函数T()。如果此时你又想要那个 “空” 的默认构造函数不需要再手写个空函数体{}这会导致它变成 “用户提供的”从而失去某些 trivial/POD 特性直接用 default让编译器恢复它的默认生成逻辑。struct Example { Example(int a); // 自定义构造 Example() default; // 强制找回默认构造且比手写 {} 更高效 }; deleteC11语义此路不通有些对象在语义上是独一无二的例如单例模式、硬件驱动句柄Mutex、FileStream它们绝不能被拷贝。在 C11 之前我们通过把拷贝构造函数设为private来防止拷贝。C11 之后可以直接在语法层面“删除” 这个函数的存在。struct Mutex { // 任何尝试拷贝代码的操作在编译期间就会报错 Mutex(const Mutex) delete; Mutex operator(const Mutex) delete; };using继承构造函数语义拿来主义派生类通常不会继承基类的构造函数。如果基类有 10 种构造方式派生类想支持同样的 10 种以前得手动写 10 个转发函数。using关键字告诉编译器把基类的构造函数直接 “引入” 到当前作用域。struct Base { Base(int); Base(std::string); Base(float); }; struct Derived: Base { using Base::Base; // 一句话拥有了上述三种构造方式 };性能优化这部分关键字主要服务于嵌入式开发和高性能计算通过向编译器提供更多信息来优化机器码。noexcept语义我保证不惹麻烦不抛出异常这是移动语义Move Semantics生效的关键。当std::vector扩容时它需要把旧数据搬到新内存。如果你的移动构造函数没有标记noexceptstd::vector为了内存安全怕搬到一半抛异常导致旧数据没了新数据也没好会放弃移动强行降级为拷贝。这在大数据量或高性能要求场景下会带来极大的损耗。class BigData { public: // 承诺移动操作绝不会失败编译器看到这个才会大胆优化 BigData(BigData other) noexcept { ... } };constexprC11/14语义在编译时就已经准备好了如果一个对象的构造参数在编译时就是确定的常量那么为什么要等到程序运行Runtime才去分配内存、赋值呢constexpr构造函数允许编译器在编译阶段就计算出对象的内存布局并直接烧录在二进制文件的只读数据端.rodata或直接作为立即数嵌入指令中。这对于嵌入式系统节省运行时开销、Flash/RAM 布局至关重要。struct Point { int x, y; constexpr Point(int _x, int _y) : x(_x), y(_y) {} }; // 编译后p 甚至可能不存在直接被优化为立即数操作 constexpr Point p(10, 20);逻辑控制与异常处理explicit在前文已经讲到。同时除了单参数构造函数多参数构造函数C11 列表初始化也需要注意。struct Vector3 { explicit Vector3(float x, float y, float z); }; void func(Vector3 v); func({1.0, 2.0, 3.0}); // 错误因为 explicit 禁止了 {list} - Object 的隐式类型转换 func(Vector3{1.0, 2.0, 3.0}); // 正确显式调用tryFunction-try block语义在进入内部前就能捕获错误构造函数分两步初始化列表 → 函数体。如果在初始化列表阶段比如基类构造、成员对象构造抛出了异常普通的try-catch包裹函数体是抓不住的。必须把try写在函数体外这就是函数 try 块。ResourceManager() try : core_resource(new core) { // ... 函数体 } catch (...) { // 能够捕获 core_resource 初始化时抛出的异常 // 注意构造函数里的 catch 必定会再次抛出异常因为对象构造函数失败了必须通知外界