在C++中,特殊成员函数(special member functions)由编译器隐式声明并可能生成,包括默认构造函数、析构函数、拷贝/移动构造与赋值,以及新引入的默认比较运算符。这些函数的生成规则取决于用户声明、成员类型和基类。规则设计鼓励零规则(Rule of Zero):依赖编译器默认行为管理资源,避免自定义。

-w700

1. 特殊成员函数种类

C++20中共8种:

  1. 默认构造函数 T() noexcept;
  2. 析构函数 ~T();
  3. 拷贝构造函数 T(const T&);
  4. 拷贝赋值运算符 T& operator=(const T&);
  5. 移动构造函数 T(T&&) noexcept;
  6. 移动赋值运算符 T& operator=(T&&) noexcept;
  7. 默认等于运算符(C++20)bool operator==(const T&) const;
  8. 三路比较运算符(C++20)std::strong_ordering operator<=>(const T&) const noexcept;

2. 生成规则详解(C++20)

2.1 默认构造函数

  • 生成:无用户声明任何构造函数时生成。
  • 行为:成员默认初始化(内置类型值初始化为零,类类型调用其默认ctor)。
  • 抑制:用户声明任何ctor,或有不可默认构造成员/基类(const/引用无初始化器)。
  • 控制= default; 生成,= delete; 禁止。

2.2 析构函数

  • 生成:无用户声明时总是生成(trivial若可能)。
  • 行为:逆序销毁成员/基类。
  • noexcept:默认noexcept(true)
  • 抑制:用户声明(含= default)。
  • 最佳:基类用虚析构= default;

2.3 拷贝构造函数

  • 生成:无用户声明拷贝ctor、移动ctor或移动assign时。
  • 行为:成员/基类逐拷贝。
  • 抑制(C++11起):用户声明移动ctor/assign,或不可拷贝成员/基类。
  • 控制= default/delete;

2.4 拷贝赋值运算符

  • 生成:无用户声明拷贝assign、移动ctor或移动assign时。
  • 行为:成员/基类逐赋值(self-assign安全)。
  • 抑制:用户声明移动ctor/assign,或不可赋值成员/基类。
  • 控制= default/delete;

2.5 移动构造函数

  • 生成:无用户声明拷贝ctor、拷贝assign、移动ctor、移动assign或析构时。
  • 行为:成员/基类逐移动(内置类型位拷贝)。
  • 抑制:用户声明拷贝ctor/assign、移动assign、析构,或不可移动成员/基类。
  • noexcept:若所有移动操作noexcept,则noexcept。
  • 控制= default/delete;

2.6 移动赋值运算符

  • 生成:同移动ctor。
  • 行为:释放旧资源,逐移动赋值。
  • 抑制:同移动ctor。
  • noexcept:同上。
  • 控制= default/delete;

2.7 默认等于运算符(C++20)

  • 生成:无用户声明==<=>时。
  • 行为:成员/基类逐==(等价关系)。
  • 签名bool operator==(const T&) const = default;
  • 抑制:不可==成员/基类(无公开==),或union有const/引用无brace-init成员。
  • 控制= default;(若可能),= delete;

2.8 三路比较运算符(C++20)

  • 生成:无用户声明<=>==operator<operator>等6个比较时。
  • 行为:成员/基类逐<=>,返回strong_ordering(若异质则partial_ordering等)。
  • 签名strong_ordering operator<=>(const T&) const noexcept = default;
  • 抑制:不可<=>成员/基类,或有异质比较需求。
  • 隐式生成<=>隐式生成==!=<><=>=
  • 控制= default;(指定strong_ordering/partial_ordering/weak_ordering),= delete;

3. 生成规则总结表

用户定义了默认构造析构函数拷贝构造拷贝赋值移动构造移动赋值
自动生成自动生成自动生成自动生成自动生成自动生成
默认构造-自动生成自动生成自动生成自动生成自动生成
析构函数自动生成-自动生成*自动生成*不生成不生成
拷贝构造不生成自动生成-自动生成*不生成不生成
拷贝赋值自动生成自动生成自动生成*-不生成不生成
移动构造不生成自动生成已删除已删除-不生成
移动赋值自动生成自动生成已删除已删除不生成-

注:* 表示该行为已在标准中标记为弃用(Deprecated),未来版本可能会改变。

4. C++20 自动比较运算符生成

C++20 引入了 operator==operator<=> 的自动生成机制,极大地简化了类类型的比较逻辑。

4.1 默认等于运算符 (operator==)

  • 自动重写:只需定义 operator==,编译器会自动推导出 operator!=
  • 重写规则a != b 会被重写为 !(a == b)
  • 成员逐位比较= default 实现会按声明顺序递归比较所有基类和成员。

4.2 三路比较运算符 (operator<=>)

  • 一箭六雕:定义 operator<=> 后,编译器会自动生成 ==, !=, <, >, <=, >=
  • 返回类型
    • std::strong_ordering:所有成员都支持强序(如 int)。
    • std::partial_ordering:包含浮点数等只支持偏序的成员。
  • 性能优化:建议同时 default operator==。虽然 <=> 能推导出 ==,但显式的 == 通常能提供更高效的相等性检查(如提前跳过长度不等的容器)。

4.3 Tips

  • 不要在 Union 中使用:编译器无法确定 union 的活跃成员,自动生成会被删除。
  • 成员顺序即逻辑:比较逻辑严格依赖成员声明顺序。改变顺序可能破坏向后兼容性。
  • 基类约束:若基类不支持比较,子类的 = default 将被隐式删除。

5. C++20 变化与最佳实践

  • Rule of Zero:优先标准容器(如unique_ptr),零自定义特殊函数。
  • Rule of Five:自定义资源管理时定义析构+4拷贝/移动。
  • noexcept:移动操作标noexcept提升容器性能(vector resize等)。
  • 删除传播:不可拷贝成员使类不可拷贝。
  • 简洁:避免边界case,用= default/delete明确意图。复杂继承/资源用组合。

5. 示例

#include <iostream>
#include <compare>  // std::strong_ordering

struct Point {
    int x, y;
    auto operator<=>(const Point&) const = default;  // 生成所有比较
};

struct Movable {
    int* ptr;
    Movable() = default;
    ~Movable() { delete ptr; }
    Movable(const Movable&) : ptr(new int(*other.ptr)) {}  // 自定义拷贝
    Movable& operator=(const Movable&) { /* impl */ return *this; }
    // 移动隐式删除,因自定义析构/拷贝
    Movable(Movable&&) = default;  // 显式启用
};

int main() {
    Point p1{1,2}, p2{1,2};
    std::cout << (p1 == p2) << '\n';  // true,隐式==

    Movable m;  // 默认ctor
    return 0;
}

6. 常见陷阱

  • 移动被抑制:自定义析构/拷贝→无隐式移动,标= default;
  • 比较union:const/引用成员需小心。
  • 异质比较<=>需指定partial_ordering
  • 虚函数:有虚析构→比较非const。

此规则确保零开销抽象,遵循KISS:简单胜于复杂。