文章目录
-
- [优先选用删除函数,而非 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` 只能用于成员函数。
假设有一个处理整数的普通函数,你想防止用户传入 double 或 char 导致隐式转换:
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) |
|---|---|---|
| 报错时机 | 外部:编译期;内部/友元:链接期 | 任何地方:编译期 |
| 适用范围 | 仅限类成员函数 | 任何函数(含非成员函数、模板特化) |
| 代码可读性 | 意图隐晦(依赖不写实现的技巧) | 意图极其明确(代码自解释) |