转向现代C++——优先选用删除函数而非private未定义函数

文章目录

    • [优先选用删除函数,而非 private 未定义函数](#优先选用删除函数,而非 private 未定义函数)
      • [Private 未定义](#Private 未定义)
      • [Delete 函数](#Delete 函数)

优先选用删除函数,而非 private 未定义函数

Private 未定义

在 C++98 中,如果想阻止用户拷贝某个类的对象(例如单例模式或不可复制的资源管理器),通常会将拷贝构造函数和拷贝赋值运算符声明为 private,并且不提供它们的实现

cpp 复制代码
class WidgetC98 {
private:
    // 1. 声明为 private 阻止了外部直接调用
    WidgetC98(const WidgetC98&);            
    WidgetC98& operator=(const WidgetC98&);

public:
    WidgetC98() {}
    void doSomething() {}
};

// 外部调用测试
void testC98() {
    WidgetC98 w1;
    // WidgetC98 w2(w1); // 编译期错误!错误信息通常是:'WidgetC98::WidgetC98' is private within this context
}

:::color4

局限性

  • 成员函数和友元仍能调用****: 如果该类内部的成员函数或者它的 friend 友元类不小心调用了拷贝构造,编译期可以通过
  • 链接期报错延迟****: 只有到了链接阶段(Link time) ,链接器发现找不到该函数的定义,才会报错(通常会报诸如 undefined reference to... 的错误)。链接期错误通常比编译期错误更难排查。

:::

Delete 函数

C++11 引入了 = delete 关键字,直接在编译期禁用了特定的函数。

cpp 复制代码
class WidgetC11 {
public:
    WidgetC11() {}

    // 1. 优先使用 = delete,且习惯上放至 public 区域
    WidgetC11(const WidgetC11&) = delete;            
    WidgetC11& operator=(const WidgetC11&) = delete;

    void doSomething() {
        // 即使在成员函数内部尝试拷贝,编译期也会立刻报错!
        // WidgetC11 wInner(*this); 
    }
};

void testC11() {
    WidgetC11 w1;
    // WidgetC11 w2(w1); // 编译期错误!错误信息极其明确:use of deleted function 'WidgetC11::WidgetC11(const WidgetC11&)'
}

:::info

优势:

  • 更早的错误捕捉 无论是外部调用,还是类内部/友元调用,全部在编译期(Compile time)直接报错,绝不留到链接期。
  • 更好的错误提示****: 编译器会明确指出该函数已被"删除(deleted)",而不是晦涩的"无法访问隐式函数"或"未定义的引用"。
  • 习惯放在 public**: ** 建议将 = delete 函数设为 public。因为当外部调用时,编译器会先检查访问权限(private),再检查删除状态。如果放 private,某些编译器只会报"无权访问"的错误,隐藏了该函数"已被禁用"的真正意图。

:::

🔊删除函数= delete 的威力不仅限于禁用拷贝构造,它可以应用在任何函数上,而 C++98 的 private` 只能用于成员函数。

假设有一个处理整数的普通函数,你想防止用户传入 doublechar 导致隐式转换:

cpp 复制代码
// 只接受真正的 int
void processInt(int value);

// 使用 = delete 禁用 float 和 double 的重载
void processInt(double) = delete;
void processInt(float) = delete;

void testConversion() {
    processInt(42);       // 正确:调用 processInt(int)
    // processInt(3.14);  // 编译期错误!尝试调用已被删除的 processInt(double)
}

如果写了一个模板函数,但想排除某些特定的类型,C++98 很难优雅地实现,而 C++11 配合 = delete 极其简单:

cpp 复制代码
// 通用模板:允许大部分指针
template<typename T>
void processPointer(T* ptr) {
    // ...
}

// 特化:明确禁止 void* 指针和 char* 指针
template<> void processPointer<void>(void*) = delete;
template<> void processPointer<char>(char*) = delete;

void testTemplate() {
    int x = 10;
    processPointer(&x);     // 正确

    void* pVoid = &x;
    // processPointer(pVoid); // 编译期错误!使用了删除的特化版本
}

在 C++98 中,如果想对模板做到这一点,只能把特化版本写进一个私有类模板里,代码会变得极其臃肿和晦涩。

特征 C++98 (private 未定义) C++11 (= delete)
报错时机 外部:编译期;内部/友元:链接期 任何地方:编译期
适用范围 仅限类成员函数 任何函数(含非成员函数、模板特化)
代码可读性 意图隐晦(依赖不写实现的技巧) 意图极其明确(代码自解释)
相关推荐
Kurisu5751 小时前
深度拆解:从 Linux 内核 Namespace 与 Cgroups 洞察容器技术的底层本质
java·linux·运维
罗超驿1 小时前
11.LeetCode 1004. 最大连续1的个数 III | 滑动窗口解法详解(Java)
java·算法·leetcode
努力发光的程序员1 小时前
面试官与程序员谢飞机的3轮Java大厂面试问答实录:涵盖Spring Boot、微服务与数据库技术
java·jvm·spring boot·redis·面试·hibernate·microservices
橙淮1 小时前
并发编程(四)
java·jvm
QiLinkOS1 小时前
发明人与专利价值共生逻辑
c语言·数据结构·c++·人工智能·单片机·嵌入式硬件·算法
z落落1 小时前
C# Stack栈 / Queue队列+所有集合 终极一页汇总(全覆盖、零遗漏)
java·开发语言·c#
王老师青少年编程1 小时前
信奥赛C++提高组csp-s之平衡树(Treap)
c++·平衡树·treap·信奥赛·csp-s·提高组
磊 子1 小时前
STL之set以及set和map区别
开发语言·c++·算法
Halo_tjn2 小时前
NIO 技术的使用
java·开发语言·nio