default 关键字强制生成默认构造函数
模拟面试开始
面试官: 我们知道,如果一个类我们不给它定义任何构造函数,编译器会自动为我们生成一个默认构造函数。类似的,编译器也可能会为我们生成拷贝构造函数、拷贝赋值运算符等。
在 C++11 之后,如果我们想显式地告诉编译器:“请为我生成一个默认的、最普通的版本”,我们可以使用 default 关键字。
请看下面这个例子:
| |
我的问题是:
在这个例子中,MyClass() = default; 这一行代码,有什么实际意义?
换句话说,如果我们删掉 MyClass() = default; 这一行,会发生什么变化?这个类还能通过 MyClass obj; 这种方式创建对象吗?为什么?
回答:
- 当用户自己定义了一种构造函数时,编译器就不会自动生成默认的无参数的构造函数。
MyClass() = default;确保无参数的构造函数存在。- 如果删去这一行,那么
MyClass这个类就无法通过MyClass obj;这种方式创建对象,因为编译器检测不到对应的构造函数。 - 而增加上这一行,可以使得程序可以在无参数的情况下创建新的对象,等到对其内容有要求时,再做修改。
面试官 (总结与肯定): 你说的没错。这个问题的核心在于 C++ 的一条规则:
一旦用户为类定义了任何一种构造函数(无论带不带参数),编译器就会“罢工”,不再自动生成默认(无参)构造函数。
所以,在你给出的例子中:
- 因为你已经定义了一个
MyClass(int val),编译器就不会再自动生成MyClass()了。 - 如果我们删掉
MyClass() = default;这一行,那么MyClass类就没有无参构造函数。此时,MyClass obj;这样的代码就会编译失败,因为它找不到匹配的构造函数。
= default 的作用:
MyClass() = default; 这一行,就是你(程序员)在向编译器明确地下指令:
“我知道我已经写了别的构造函数,这通常会阻止你生成默认构造函数。但是,我仍然需要一个公开的、默认的无参构造函数,请你现在就为我生成一个。”
使用 = default 的好处:
- 意图明确:它清晰地表达了“我需要默认行为”的意图。
- 效率更高:由编译器生成的
= default版本,通常比我们自己手写一个空的{}函数体效率更高。因为编译器可能会把它标记为trivial(平凡的),从而进行更多的优化。 - 维持类的特性:在某些复杂的场景下,手写的
{}版本可能会破坏类的POD(Plain Old Data) 或trivial属性,而= default则不会。
你对 = default 的理解已经很到位了。
delete 关键字用于禁用函数
面试官 (引入 delete): 好的,我们知道了 = default 是用来 “强制生成” 默认版本的。
现在我们来看与它相对的另一个关键字:= delete;。它的作用正好相反,是用来 “明确禁用” 某个函数。
我们之前讨论过,对于一个包含裸指针的类,编译器自动生成的拷贝构造函数(执行浅拷贝)是危险的。在 C++98 时代,为了阻止别人拷贝这种对象,程序员们通常会耍一个小花招:把拷贝构造函数声明在 private 域中,并且只声明,不实现。
| |
这样,任何试图在类外调用拷贝构造函数的代码都会因为“私有成员不可访问”而编译失败。
我的问题是:
C++11 引入的 = delete; 语法,是如何更清晰、更强大地解决“禁止拷贝”这个问题的?它和 C++98 的 private 技巧相比,有什么优势?
回答:
- 更清晰:
= delete;语法能显式地告诉编译器:这个函数不允许被调用。 - 更强大:相比“把拷贝函数声明在
private域中的方法”,直接使用delete声明,能禁止来自所有地方的调用。不光能禁止来自类外的调用,还能禁止来自类内或者友元的调用。
具体的语法如下:
| |
面试官 (总结与肯定): 你说的完全正确。
= delete vs C++98 private 技巧
清晰性 (Clarity):
= delete:意图非常明确。任何看到... = delete;的人,都会立刻明白“这个函数被禁用了”。- private 技巧:是一种“hack”。它利用了访问权限来间接达到目的。当编译失败时,给出的错误信息通常是“
...is private”,这可能会让不熟悉这个技巧的开发者感到困惑,他可能会以为是权限问题,而不是这个函数本身就不该被调用。
彻底性 (Completeness):
= delete:这是一种绝对的禁用。任何代码,包括成员函数和友元函数,都不能调用被= delete的函数。- private 技巧:无法阻止成员函数和友元函数调用它。虽然这在“禁止拷贝”的场景下不常见,但它暴露了这种技巧的不完备性。
适用范围 (Applicability):
= delete:这是一个通用的机制。它不仅可以用来禁用特殊的成员函数,还可以用来禁用任何你想禁用的函数,包括普通函数!这个特性在防止不期望的类型隐式转换时非常有用。1 2 3 4 5 6// 禁止通过 int 调用 void process(double d); void process(int i) = delete; process(3.14); // OK process(10); // Error! 调用了被 delete 的函数- private 技巧:只能用于类的成员函数。
结论:
在现代 C++ 中,如果你想禁用某个由编译器自动生成的成员函数(比如拷贝构造),或者想禁止某个特定签名的函数被调用,永远都应该优先使用 = delete。它更清晰、更安全、也更通用。
总结
面试结束
今天我们系统地学习了 C++11 引入的两个用于控制特殊成员函数的重要关键字:
= default:用于在特定情况下,强制编译器生成默认的函数版本。= delete:用于明确地禁用任何函数(包括特殊成员函数和普通函数)。
这两个关键字,让类的设计者能够更精确地掌控类的行为,写出意图更明确、也更安全的代码。你今天的表现非常出色,对这两个现代 C++ 特性的理解非常到位。