在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. 生成规则总结表

特殊函数默认生成条件主要抑制条件
默认ctor无任何ctor声明任何ctor声明,不可默认构造成员
析构无声明用户声明
拷贝ctor无拷贝ctor/移动ctor/移动assign移动ctor/assign声明,不可拷贝成员
拷贝assign无拷贝assign/移动ctor/移动assign移动ctor/assign声明,不可赋值成员
移动ctor无拷贝ctor/assign/移动ctor/assign/析构拷贝/析构声明,不可移动成员
移动assign同移动ctor同移动ctor
== (C++20)无==/<=>声明不可==成员/基类
<=> (C++20)无比较运算符声明不可<=>成员/基类

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

  • 比较运算符:自动化成员比较,简化==/<等。= default; <=> 隐式所有6运算符。
  • 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:简单胜于复杂。