C++ 构造函数语义学

构造函数是C++中用于初始化对象的特殊成员函数,其语义学涉及编译器的处理方式、对象的构造过程以及与内存管理的交互。以下是C++构造函数语义学的关键方面:

1. 构造函数的分类

默认构造函数

  • 当类中没有定义任何构造函数时,编译器会生成一个默认构造函数

  • 如果用户定义了任何构造函数,编译器不会生成默认构造函数(除非显式使用= default

    复制代码
    class MyClass {
    public:
        MyClass() {} // 默认构造函数
    };

拷贝构造函数

复制代码
class MyClass {
public:
    MyClass(const MyClass& other) { /* 拷贝逻辑 */ }
};

用于通过同类型的另一个对象初始化新对象

  • 形式为X(const X&)X(X&)
  • 如果用户没有定义,编译器会生成一个默认的成员逐个拷贝的拷贝构造函数

移动构造函数 (C++11)

复制代码
class MyClass {
public:
    MyClass(MyClass&& other) noexcept { /* 移动资源 */ }
};

用于"移动"资源而不是拷贝资源

  • 形式为X(X&&)
  • 如果用户没有定义,且类满足特定条件,编译器会生成默认的移动构造函数

转换构造函数

  • 接受单个参数的构造函数,可用于隐式类型转换

  • 可以使用explicit关键字禁止隐式转换

    class MyClass {
    public:
    explicit MyClass(int) {} // 禁止隐式转换
    };

=default 和 =delete

复制代码
class MyClass {
public:
    MyClass() = default; // 显式要求编译器生成默认实现
    MyClass(const MyClass&) = delete; // 禁止拷贝
};
  • =default:显式要求编译器生成默认实现

  • =delete:禁止特定操作(如拷贝构造)

2. 构造函数的调用语义

直接初始化 vs 拷贝初始化

复制代码
MyClass obj1; // 默认构造
MyClass obj2(); // 函数声明,不是构造!
MyClass obj3{}; // 值初始化
MyClass obj4(arg); // 直接初始化
MyClass obj5 = arg; // 拷贝初始化(可能被优化)
MyClass obj6 = {arg}; // 列表初始化

继承中的构造函数

  • 派生类构造函数在初始化列表中调用基类构造函数

  • 如果没有显式调用,会尝试调用基类的默认构造函数

    class Base {
    public:
    Base(int) {}
    };

    class Derived : public Base {
    public:
    using Base::Base; // 继承Base的构造函数 (C++11)

    复制代码
      // 或者显式调用基类构造函数
      Derived(int x) : Base(x) {}

    };

3. 构造函数的实现语义

编译器优化

  • 返回值优化(RVO):编译器可能消除临时对象的构造
  • 命名返回值优化(NRVO):对命名返回值的优化

构造函数的异常处理

  • 如果构造函数抛出异常,已构造的成员和基类子对象会被自动析构
  • 资源管理类(RAII)在构造函数异常时仍能保证资源释放

4. 特殊成员函数的生成规则 (C++11起)

复制代码
class Example {
public:
// 用户声明或删除以下任一特殊成员函数会影响其他函数的生成
Example(); // 默认构造函数
Example(const Example&); // 拷贝构造函数
Example(Example&&); // 移动构造函数
Example& operator=(const Example&); // 拷贝赋值
Example& operator=(Example&&); // 移动赋值
~Example(); // 析构函数
};

生成规则(C++17):

  1. 默认构造函数:仅当类没有用户声明的构造函数时生成
  2. 析构函数:总是生成(除非用户声明了删除的析构函数)
  3. 拷贝构造函数:仅当类没有用户声明的移动操作或析构函数时生成
  4. 拷贝赋值运算符:同上
  5. 移动构造函数和移动赋值运算符:仅当类没有用户声明的拷贝操作或析构函数时生成

5. 构造函数的内存语义

对象构造过程

  1. 分配内存(栈或堆)
  2. 初始化虚表指针(如果有多态)
  3. 按声明顺序构造基类子对象
  4. 按声明顺序构造成员变量
  5. 执行构造函数体

成员初始化顺序

  • 成员变量总是按照它们在类中声明的顺序初始化
  • 初始化列表中的顺序不影响实际初始化顺序

6. 现代C++中的构造函数改进

委托构造函数 (C++11)

复制代码
class MyClass {
public:
    MyClass() : MyClass(0, 0) {} // 委托给另一个构造函数
    MyClass(int a, int b) : a_(a), b_(b) {}
private:
    int a_, b_;
};
  • 一个构造函数可以调用同类的另一个构造函数

  • 避免代码重复

继承构造函数 (C++11)

复制代码
class Base {
public:
    Base(int) {}
};

class Derived : public Base {
public:
    using Base::Base; // 继承Base的构造函数 (C++11)
    
    // 或者显式调用基类构造函数
    Derived(int x) : Base(x) {}
};
  • 派生类必须初始化其直接基类

  • C++11允许使用using声明继承基类构造函数

列表初始化 (C++11)

复制代码
class Point {
public:
int x, y;
Point(int a, int b) : x(a), y(b) {}
Point(std::initializer_list<int> init) {
auto it = init.begin();
x = *it++;
y = *it;
}
};


Point p1{1, 2}; // 调用initializer_list构造函数
Point p2(1, 2); // 调用常规构造函数

构造顺序

  1. 基类构造函数(按继承顺序)

  2. 成员变量构造函数(按声明顺序)

  3. 构造函数体执行

注意事项

  1. 虚函数在构造函数中调用的是当前类的实现,不是派生类的

  2. 构造函数不应调用虚函数来实现多态行为

  3. 构造函数可以抛出异常,但需要妥善处理资源

  4. RAII(资源获取即初始化)是构造函数的重要应用模式

理解这些构造函数语义有助于编写更安全、更高效的C++代码,特别是在涉及资源管理、对象生命周期和

相关推荐
程序员编程指南17 分钟前
Qt 开发自动化测试框架搭建
c语言·开发语言·c++·qt
三小尛28 分钟前
C++赋值运算符重载
开发语言·c++
籍籍川草31 分钟前
JVM指针压缩的那些事
java·开发语言·jvm
小徐不徐说39 分钟前
C++ 模板与 STL 基础入门:从泛型编程到实战工具集
开发语言·数据结构·c++·qt·面试
艾莉丝努力练剑40 分钟前
【C/C++】类和对象(上):(一)类和结构体,命名规范——两大规范,新的作用域——类域
java·c语言·开发语言·c++·学习·算法
froginwe111 小时前
WebPages PHP:深入解析PHP在网页开发中的应用
开发语言
R-G-B2 小时前
【33】C# WinForm入门到精通 ——表格布局器TableLayoutPanel【属性、方法、事件、实例、源码】
开发语言·c#·c# winform·表格布局器·tablelayoutpane
郝学胜-神的一滴2 小时前
Spring Boot Actuator 保姆级教程
java·开发语言·spring boot·后端·程序人生
赵英英俊2 小时前
Python day31
开发语言·python
程序员-Queen3 小时前
RDQS_c和RDQS_t的作用及区别
c语言·开发语言