乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

= default 与 = delete

定义与作用

C++11 引入了两个新的函数声明语法:

语法含义使用场景
= default显式要求编译器生成默认实现恢复被抑制的默认特殊成员函数
= delete显式禁止使用该函数阻止拷贝、阻止隐式转换、阻止特定重载
class NonCopyable {
public:
    NonCopyable() = default;                      // 使用编译器生成的默认构造
    NonCopyable(const NonCopyable&) = delete;     // 禁止拷贝
    NonCopyable& operator=(const NonCopyable&) = delete;
};

核心原理

特殊成员函数的默认生成规则

=delete 与 =default 的对比

特性= default= delete
作用使用编译器默认行为禁止使用
可修饰特殊成员函数(构造/析构/拷贝/移动)任意函数
出现位置类内声明 + 类外定义声明处(必须)
适用性仅编译期可生成的函数任何函数(包括自由函数)
本质"请生成这个""不能用这个"

完整示例

示例一:从 Rule of Three 到 Rule of Zero

场景说明:对比三种资源管理策略——手写所有函数、全部 = default、Rule of Zero。

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// C++98 风格:手写所有特殊成员函数(繁琐易错)
class Course98 {
public:
    Course98(std::string name) : name_(new std::string(std::move(name))) {
        std::cout << "[98-构造] " << *name_ << std::endl;
    }

    ~Course98() { delete name_; }

    Course98(const Course98& other)
        : name_(new std::string(*other.name_)) {
        std::cout << "[98-拷贝]" << std::endl;
    }

    Course98& operator=(const Course98& other) {
        if (this != &other) {
            delete name_;
            name_ = new std::string(*other.name_);
        }
        return *this;
    }

    // 没有移动语义——无法利用临时对象优化

    const std::string& getName() const { return *name_; }

private:
    std::string* name_;  // 裸指针管理资源
};

// C++11 风格:全部 = default(但需要正确使用智能指针)
class Course11 {
public:
    Course11(std::string name) : name_(std::make_unique<std::string>(std::move(name))) {
        std::cout << "[11-构造] " << *name_ << std::endl;
    }

    // 有 unique_ptr 成员,拷贝操作被自动删除
    // 移动操作自动生成(因为成员 unique_ptr 可移动)

    const std::string& getName() const { return *name_; }

private:
    std::unique_ptr<std::string> name_;
};

// Rule of Zero 风格:不声明任何特殊成员函数
class CourseModern {
public:
    CourseModern(std::string name) : name_(std::move(name)) {
        std::cout << "[Modern-构造] " << name_ << std::endl;
    }

    // 不需要任何特殊成员函数——编译器自动生成全部正确的
    const std::string& getName() const { return name_; }

private:
    std::string name_;  // std::string 自身已正确管理资源
};

int main() {
    std::cout << "===== C++98 风格(裸指针) =====" << std::endl;
    {
        Course98 c1("数据结构");
        Course98 c2 = c1;  // 深拷贝
        std::cout << "c1: " << c1.getName() << std::endl;
        std::cout << "c2: " << c2.getName() << std::endl;
    }

    std::cout << "\n===== C++11 风格(unique_ptr) =====" << std::endl;
    {
        Course11 c1("操作系统");
        // Course11 c2 = c1;  // 编译错误:unique_ptr 不可拷贝
        Course11 c2 = std::move(c1);  // 移动:c1 的资源转移给 c2
        std::cout << "c2: " << c2.getName() << std::endl;
        // c1 不再持有资源
    }

    std::cout << "\n===== Rule of Zero(std::string) =====" << std::endl;
    {
        CourseModern c1("计算机网络");
        CourseModern c2 = c1;             // 拷贝
        CourseModern c3 = std::move(c2);  // 移动
        std::cout << "c1: " << c1.getName() << std::endl;
        std::cout << "c3: " << c3.getName() << std::endl;
    }

    return 0;
}

预期输出:

===== C++98 风格(裸指针) =====
[98-构造] 数据结构
[98-拷贝]
c1: 数据结构
c2: 数据结构

===== C++11 风格(unique_ptr) =====
[11-构造] 操作系统
c2: 操作系统

===== Rule of Zero(std::string) =====
[Modern-构造] 计算机网络
c1: 计算机网络
c3: 计算机网络

逐段分析:

  • Course98 使用裸指针,必须手写析构 + 拷贝构造 + 拷贝赋值(Rule of Three),且不能享受移动优化
  • Course11 使用 unique_ptr——拷贝被自动删除,移动自动生成。但仍然需要写析构?不需要!unique_ptr 自动释放
  • CourseModern 使用 std::string——Rule of Zero:不声明任何特殊成员函数,编译器全自动生成,且全部正确
  • Rule of Zero 是最高目标:让每个成员自己管理资源,类本身不操心

示例二:=delete 的多种应用场景

#include <iostream>

// 场景 1:禁止拷贝
class DatabaseConnection {
public:
    DatabaseConnection(const char* conn) {
        std::cout << "连接数据库: " << conn << std::endl;
    }

    DatabaseConnection(const DatabaseConnection&) = delete;
    DatabaseConnection& operator=(const DatabaseConnection&) = delete;

    // 但允许移动(如果需要)
    DatabaseConnection(DatabaseConnection&&) = default;
};

// 场景 2:禁止特定类型的隐式转换
void safe_int_only(int x) {
    std::cout << "safe: " << x << std::endl;
}

void safe_int_only(double) = delete;  // 禁止 double 重载
void safe_int_only(long) = delete;    // 禁止 long 重载

// 场景 3:禁止堆上分配
class StackOnly {
public:
    StackOnly() { std::cout << "StackOnly 构造" << std::endl; }

    // 禁止 new
    void* operator new(std::size_t) = delete;
};

// 场景 4:禁止特定模板实例化
template <typename T>
void process(T value) {
    std::cout << "通用处理: " << value << std::endl;
}

template <>
void process<double>(double) = delete;  // 禁止处理 double

int main() {
    std::cout << "===== 场景 1: 禁止拷贝 =====" << std::endl;
    DatabaseConnection db1("mysql://localhost");
    // DatabaseConnection db2 = db1;  // 编译错误

    std::cout << "\n===== 场景 2: 禁止隐式转换 =====" << std::endl;
    safe_int_only(42);
    // safe_int_only(3.14);  // 编译错误:double 版本被 delete
    // safe_int_only(100L);  // 编译错误:long 版本被 delete

    std::cout << "\n===== 场景 3: 禁止堆分配 =====" << std::endl;
    StackOnly so;  // OK,栈上
    // StackOnly* p = new StackOnly();  // 编译错误

    std::cout << "\n===== 场景 4: 禁止特定模板实例化 =====" << std::endl;
    process(42);
    process(std::string("hello"));
    // process(3.14);  // 编译错误

    return 0;
}

预期输出:

===== 场景 1: 禁止拷贝 =====
连接数据库: mysql://localhost

===== 场景 2: 禁止隐式转换 =====
safe: 42

===== 场景 3: 禁止堆分配 =====
StackOnly 构造

===== 场景 4: 禁止特定模板实例化 =====
通用处理: 42
通用处理: hello

逐段分析:

  • = delete 可以应用于任意函数,不仅限于特殊成员函数
  • 删除 operator new 可以强制对象只能在栈上创建
  • 删除特定类型重载或模板特化,是更精确的"重载禁止"方案(优于 SFINAE 的可读性)
  • = delete 与 = default 都可以在类外定义时使用(仅特殊成员函数)

易错场景与面试考点

易错场景

场景错误表现正确做法
移动操作被声明但拷贝操作未处理拷贝被隐式删除要么全部 default,要么全部自定义
= default 在类内时自动 inline影响编译/链接策略若要不 inline,在类外 = default
= delete 只写声明不写定义(C++98 风格)链接时错误而非编译时C++11 起用 = delete 直接编译时拒绝
模板中的 = delete 偏特化语法限制函数模板不支持偏特化,需用重载 + delete
= default 在成员有 const/引用时编译器无法生成手写构造函数

常见面试问题

  1. = default 和 = delete 的作用和使用场景?——= default 要求编译器生成默认实现(如恢复被用户声明抑制的移动操作)。= delete 显式禁止使用(如禁用拷贝、阻止隐式转换)。

  2. Rule of Zero 和 Rule of Five 的关系?——Rule of Zero 是最高目标:让每个成员自行管理资源,类本身不声明任何特殊成员函数。当必须管理资源时才用 Rule of Five。

  3. = delete 和 private(C++98)禁止拷贝的区别?——= delete 在编译时就报错,错误信息明确;private 可能延迟到链接时才报错,且友元和成员仍可访问。

  4. 移动操作和拷贝操作的自动生成相互影响?——声明了移动操作,拷贝操作被隐式删除。声明了拷贝操作或析构函数,移动操作不自动生成。

  5. 哪些函数不能用 = default?——只有编译器可以生成的函数(默认构造、析构、拷贝/移动构造和赋值)才能 = default。带参数的非特殊成员函数必须手写。

小结

  • = default 显式要求编译器生成默认实现,= delete 显式禁止函数使用
  • Rule of Zero 是现代 C++ 的最高准则:让成员自己管资源,类不声明特殊成员函数
  • = delete 不仅是"禁止拷贝",还可禁止特定重载、特定模板实例化、特定转换
  • 编译器自动生成规则复杂——涉及移动时尤其容易出错,= default 让意图明确
  • C++11 的 = delete 优于 C++98 的 private + 不实现——编译时报错而非链接时
上一页
友元