3.8、default/delete(显式默认/删除成员函数)

什么是=default和=delete?

  • • =default:告诉编译器“请帮我生成这个特殊成员函数的默认实现”,而不是自己写函数体。它可以恢复那些因为你写了其他构造函数而不自动生成的默认函数,或者显式声明默认行为,提升代码可读性和优化机会。
  • • =delete:告诉编译器“这个函数我不想让你生成,也不允许调用”,用来禁止某些函数的使用,比如禁止拷贝构造或赋值操作,避免错误的对象复制和赋值。

传统写法 vs C++11写法对比

传统写法:禁止拷贝的笨办法

class NonCopyable {
private:
    NonCopyable(const NonCopyable&);            // 声明但不定义
    NonCopyable& operator=(const NonCopyable&); // 声明但不定义
public:
    NonCopyable() {}
};

这种写法靠私有化并不定义函数来阻止拷贝,但:

  • • 代码晦涩,容易被误用。
  • • 编译错误信息不明确。
  • • 不能禁止移动操作。

C++11写法:显式默认和删除

class NonCopyable {
public:
    NonCopyable() = default;                    // 显式默认构造函数
    NonCopyable(const NonCopyable&) = delete;  // 禁止拷贝构造
    NonCopyable& operator=(const NonCopyable&) = delete// 禁止拷贝赋值
};

这段代码:

  • • 明确表达设计意图,代码更清晰。
  • • 编译器会生成更友好的错误信息。
  • • 禁止拷贝操作同时允许移动操作(除非也删除移动函数)。

设计哲学:为什么要有=default和=delete?

  • • 明确控制特殊成员函数的生成:传统C++中,编译器自动生成特殊成员函数,但规则复杂且容易被用户定义的函数影响,导致默认函数缺失。=default让你显式恢复默认实现。
  • • 防止错误使用函数=delete提供了语言层面禁止函数调用的机制,比传统私有声明更安全、更直观。
  • • 提升代码可读性和维护性:通过显式声明,代码意图一目了然,减少误用和隐藏bug。
  • • 帮助编译器优化:显式默认的函数可能被编译器视为“平凡函数”,有助于优化。

最佳使用场景

  • • 恢复默认构造函数:当你定义了其他构造函数但仍想保留默认构造时,使用=default
  • • 禁止拷贝和赋值:设计不可复制类时,使用=delete删除拷贝构造和赋值运算符。
  • • 禁止特定函数重载:通过=delete禁止某些函数重载,避免隐式类型转换导致的错误调用。
  • • 简化特殊成员函数定义:用=default代替空函数体,提升代码简洁度。

优缺点总结

优点 缺点
明确表达设计意图,代码更易读、易维护 需要程序员理解特殊成员函数生成规则,初学者有学习成本
编译器生成默认函数更高效,利于性能优化 误用=delete可能导致接口限制过严,影响灵活性
=delete防止错误调用,提升代码安全性 旧代码迁移时需要逐步添加,存在兼容性问题
支持禁止任意函数调用,不仅限于特殊成员函数 过度删除函数可能导致代码复用受限

常见误用及后果

  • • 忘记写=default导致默认构造函数缺失:定义了带参数构造函数后,默认构造函数不再自动生成,若未显式=default,会导致编译错误。
  • • 错误删除移动构造函数或移动赋值运算符:删除拷贝函数时未考虑移动函数,可能导致类不能移动,影响性能。
  • • 滥用=delete限制接口:过度删除函数,限制了类的灵活使用和扩展。
  • • 对非特殊成员函数使用=default:只有特殊成员函数可用=default,否则编译错误。

代码示例:显式默认与删除的综合运用

#include <iostream>

class Widget {
public:
    Widget() = default;                  // 显式默认构造函数
    Widget(int val) : value(val) {}

    Widget(const Widget&) = delete;     // 禁止拷贝构造
    Widget& operator=(const Widget&) = delete// 禁止拷贝赋值

    Widget(Widget&&) = default;         // 允许移动构造
    Widget& operator=(Widget&&) = default// 允许移动赋值

    void print() const { std::cout << "Value: " << value << std::endl; }

private:
    int value = 0;
};

int main() {
    Widget w1(10);
    Widget w2 = std::move(w1);  // 移动构造,合法

    // Widget w3(w1);           // 编译错误,拷贝构造被删除
    // w2 = w1;                 // 编译错误,拷贝赋值被删除

    w2.print();
    return 0;
}

总结:=default与=delete的独到价值

=default=delete是C++11对类设计的精细化控制,它们不仅让程序员能够精确掌控特殊成员函数的生成和禁用,还提升了代码的安全性和可维护性。它们体现了现代C++追求明确意图、减少隐式行为、提升代码质量的设计哲学。

我认为,显式默认和删除函数的引入,是C++语言向更严谨、更现代、更易维护方向迈出的关键一步。合理运用它们,能让你的代码既简洁又健壮,避免传统C++中隐藏的陷阱和误用。
本文首发于【讳疾忌医-note】公众号,未经授权,不得转载。
(加入我的知识星球,免费获取账号,解锁所有文章。)

 

阅读剩余
THE END