已删除构造 函数使用
🧑💻 User
In this lecture, we’re going to learn about the leading constructors.And this is a feature we have in C plus plus to disable some constructors and prevent them from beingused to build objects.And the syntax to do that is ridiculously easy.All you have to do is say equals and say delete and put a semicolon at the end.And from this point on you won’t have to implement this constructor.And anytime somebody tries to use a syntax that is going to call this constructor, they are going toget a compiler error saying that they can’t use this constructor because it was deleted.So for example, here we have disabled the default non parameter constructor.We have disabled the copy constructor and we have disabled the Move constructor.If we try to use them, we’re going to get a compiler error.So let’s head over to Visual Studio code and try this on some real code.Okay, so here we are in our working folder.The current project is deleted constructors.We are going to grab our template files like we always do and we’re going to paste that in our lecture,which is deleted constructors and we’re going to grab the class from the last lecture, which is thepoint class here, and we’re going to reuse it in this lecture because we don’t want to redesign it.So let’s paste that in here and then we’re going to open this in Visual Studio code.By dragging and dropping here, this is going to open our project.We can open the Main.cpp file, we can open our class, we can open the files for our class point eightand point CP, and then we’re going to close the sidebar here.So what we want to do is to mark a few constructors as deleted, so we want to delete the default nonparameter constructor.So what we’re going to do in this class is say point, and then we’re going to say that this constructorwas deleted by using this syntax here we’re going to say equals and say delete.And this constructor from this point on will be prevented from being used in this class here.We’re going to go down and delete all the other constructors we don’t need.We are just going to leave in this constructor.That’s what we want people to use to build our objects.Here we have this much control so we can disable the copy constructor.We’re going to delete it and we’re going to disable the Move constructor.We’re also going to delete it.And after you delete the constructor, you don’t have to implement that anymore in the CPP file.So what we’re going to do is comment out all these constructors we don’t want, so we’re going to commentout the Move constructor, we’re going to comment out the copy constructor and we’re not going to implementthe non parameter default constructor.And you see that in our header files.We don’t have any squiggly lines because this constructors are not implemented.Let’s go back in main and use our point class.We’re going to include that, we’re going to include point and then we’re going to set up an objectof point using the constructor that we support now.So we’re going to say point and say P one and we’re going to use two parameters or two arguments.We’re going to put in a few numbers here.And we’re going to build an object and for example, print information about this object.So we’re going to say P one print info and you’re going to see that this is going to work just fine.If we build, the world is going to be good, as you see here, and we kind of bring up a terminal andactually try this out, we can clear and run Rooster.We’re going to see that we have the information about our point and everything is as we expect.But if we try to use other constructors, let’s try to do that and use a non parameter constructor.By setting up our point like this.We’re going to get a compiler error here because we’re not allowed to use this constructor.If we go in, we’re going to say the default constructor of point cannot be referenced.It is a deleted function.We have deleted it and people can’t use it to build the objects.And this is what you achieve with deleted constructors.We have also deleted a couple of other constructors.For example, the copy constructor is deleted.So if we try to build an object from the copy constructor, we’re going to get a compiler error.Let’s do that.We’re going to set up another point and we’re going to say p zero, for example, and we’re going toput in some data.Let’s put in 4.6 and 5.2.We can do this and then we’re going to try and construct P one from P zero and we’re going to do somethinglike this.This is going to call the copy constructor.We need to use a dot here, not a comma for p zero to be valid here.But you see that if we try to call the copy constructor, we’re going to be prevented from doing this.Because point the.Copy constructor has been deleted.Okay, so we don’t see the entire thing here, but if we hover over here, we see that the functionthat we are trying to call is a deleted function.Okay, so we can’t call this.And if we try to build our program and ignore what we see from Visual Studio code, we’re going to geta compiler error and it is going to say use of deleted function.The copy constructor was deleted.You can’t build objects using the copy constructor.We can also see that the Move constructor was deleted.We’re going to set up a syntax which is going to build an object in place and it is essentially goingto be a temporary.So we’re going to grab what we have here and we’re going to cut it out and then we’re going to say Stdmoveand we’re going to pass in our temporary here.And this is a setup that is going to call the Move constructor.But again, this is not going to work because the Move constructor has been deleted.If we go in our class, we’re going to see that it has been deleted, so we can’t really build objectsfrom our Move constructor.Let’s see what Visual Studio code is saying here.Use of deleted function const point reference.This is not very clear, but we can try to build and see what we get.We’re going to get a compiler error and we’re going to see that we are trying to use a function thathas been deleted and that function is the Move constructor.So it has been deleted and we cannot use it.So and you see that Visual Studio code has came to its senses.It is saying that it is the Move constructor, but this is really all this lecture is about.If you don’t want a constructor to be used to build objects, you can delete it and people are goingto get a compiler error if they try to use it.Okay.Some of you might be asking why would I want to delete my constructor?Because what we want as developers is to give people more flexibility in using what we develop.But sometimes it really doesn’t make sense to do some things.For example, sometimes you don’t want your object copied because copying objects is going to make yourdesign really shaky and sometimes you really want this.Unfortunately, I don’t have an easy example I can give you right now and you are definitely going tocome across this in your career as a C plus plus developer.So note that you can delete your constructors and disable people from using them to build objects ofyour classes.This is really all we set out to do in this lecture.I hope you found it interesting.We are going to stop here in this one and the next one we’re going to learn about initializer List constructors.Go ahead and finish up here and meet me there.
🤖 Assistant
好的,C++学习者你好!很高兴能成为你的C++导师。今天我们将深入探讨一个非常重要的C++特性:已删除的构造函数。这个概念能让你更好地控制类的行为和对象的创建。
C++ 进阶:深入理解已删除的构造函数 (= delete
)
摘要: 本节课将详细讲解C++中如何使用= delete
语法来禁用(删除)特定构造函数,从而阻止用户通过这些方式创建对象。我们将探讨其目的、语法、应用场景以及相关注意事项,并通过代码示例加深理解。
1. 什么是已删除的构造函数?(What Are Deleted Constructors?)
在C++中,= delete
语法是一种强大的机制,允许我们显式地禁用(或者说“删除”)类的特定成员函数,最常见的应用就是删除构造函数 (constructor) 和赋值运算符 (assignment operator)。当一个构造函数被标记为“已删除”后,任何试图调用该构造函数来创建对象的代码都将在编译时 (compile time) 收到错误,从而有效阻止了此类对象的生成。
核心思想:
想象一下你设计了一个复杂的类,你希望使用者只能通过某种特定方式来创建这个类的对象,而禁止其他方式(比如不允许拷贝、不允许默认构造等)。这时,你就可以使用“已删除的构造函数”来实现这种限制。
2. 为什么需要已删除的构造函数?(Why Do We Need Deleted Constructors?)
你可能会问:“为什么我要禁用构造函数呢?作为开发者,不是应该提供更多灵活性吗?” 这通常出于以下几个重要的设计考虑:
资源管理与唯一性 (Resource Management and Uniqueness):
- 示例:管理唯一资源 假设你的类管理着一个独占性的资源,比如一个文件句柄、一个网络连接或者一个硬件设备接口。这类资源通常是唯一的,不能被随意拷贝或移动。如果允许拷贝,可能会导致多个对象试图管理同一个资源,造成双重释放 (double free) 或资源竞争 (resource contention) 等问题。通过删除拷贝构造函数 (copy constructor) 和移动构造函数 (move constructor),你可以强制确保对象是独一无二的,从而避免这些复杂的资源管理问题。
std::unique_ptr
就是一个很好的例子,它就是通过删除拷贝构造函数来实现其独占性语义的。
- 示例:管理唯一资源 假设你的类管理着一个独占性的资源,比如一个文件句柄、一个网络连接或者一个硬件设备接口。这类资源通常是唯一的,不能被随意拷贝或移动。如果允许拷贝,可能会导致多个对象试图管理同一个资源,造成双重释放 (double free) 或资源竞争 (resource contention) 等问题。通过删除拷贝构造函数 (copy constructor) 和移动构造函数 (move constructor),你可以强制确保对象是独一无二的,从而避免这些复杂的资源管理问题。
强制特定构造方式 (Enforcing Specific Construction Methods):
- 你可能希望用户只能通过提供特定参数来初始化对象,而不是使用默认构造函数。例如,一个
Point
类可能要求必须提供X和Y坐标才能创建,不允许默认构造一个未定义坐标的点。
- 你可能希望用户只能通过提供特定参数来初始化对象,而不是使用默认构造函数。例如,一个
防止不合理的操作 (Preventing Illogical Operations):
- 有些类的概念设计上就不应该被拷贝。例如,一个表示“线程”的类,拷贝一个运行中的线程是没有意义的,甚至可能导致程序崩溃。
接口设计与控制 (Interface Design and Control):
- 通过禁用某些构造函数,你可以在API层面就明确告诉用户:“这个类的对象不能这样创建。”这有助于开发者更好地理解和正确使用你的类。
3. 如何使用已删除的构造函数?(How To Use Deleted Constructors?)
使用已删除的构造函数非常简单,只需要在构造函数的声明 (declaration) 后加上 = delete;
即可。
语法:
1 | ClassName() = delete; // 禁用默认构造函数 |
关键点:
- 声明处禁用:
= delete;
必须放在函数的声明处,通常在类的头文件 (.h 或 .hpp) 中。 - 无需实现: 一旦一个函数被标记为
= delete;
,你就 不需要 (也无法) 在对应的 .cpp 文件中提供它的定义 (definition) 或实现 (implementation)。如果尝试提供实现,编译器会报错。 - 编译时错误: 任何尝试调用已删除构造函数的代码都会在编译阶段被拦截,生成清晰的编译器错误信息,告知用户该函数已被删除,不能使用。
接下来,我们将通过具体的代码示例来展示如何删除不同类型的构造函数。
4. 代码示例 (Code Example)
我们以一个简单的 Point
类为例,演示如何删除其默认构造函数、拷贝构造函数和移动构造函数。我们只允许通过提供X和Y坐标来创建 Point
对象。
Point.h
(头文件):
1 |
|
Point.cpp
(源文件):
1 |
|
main.cpp
(主程序文件):
1 |
|
编译和运行:
当你尝试编译上述 main.cpp
文件时,如果你解除了注释那些尝试调用已删除构造函数的行,编译器会立即报错。
例如,对于 Point p_default;
,你可能会看到类似如下的错误信息(具体信息可能因编译器而异,但大意相同):error: call to deleted constructor of 'Point'
或者error: use of deleted function 'Point::Point()'
这正是我们想要的效果:在程序运行之前,就阻止了不符合设计意图的对象创建。
5. QA 闪卡 (QA Flashcards)
Q1: Point() = delete;
这行代码在C++中有什么作用?
A1: 它声明 Point
类的默认构造函数被显式删除,这意味着你不能再通过 Point obj;
或 Point obj{};
这样的方式来创建 Point
类的对象。任何尝试都会导致编译错误。
Q2: 如果一个构造函数被标记为 = delete;
,我还需要在 .cpp
文件中为它提供实现吗?
A2: 不需要。 恰恰相反,如果一个函数被标记为 = delete;
,你不能为它提供实现。尝试这样做会导致编译错误。
Q3: 尝试使用已删除的构造函数会发生什么?
A3: 编译器会立即报错,阻止程序编译成功。这是一种编译时错误,能有效避免运行时出现意外行为。
Q4: 除了构造函数,= delete;
还可以用来删除哪些C++类的成员函数?
A4: = delete;
不仅可以删除各种构造函数(默认、拷贝、移动),还可以删除拷贝赋值运算符 (copy assignment operator)、移动赋值运算符 (move assignment operator) 以及其他成员函数(包括非特殊成员函数)。
Q5: 请举一个删除构造函数的实际应用场景。
A5: 最常见的场景是设计一个管理独占性资源(如文件句柄、数据库连接)的类。为了防止资源被多重管理导致问题,我们会删除其拷贝构造函数和拷贝赋值运算符,甚至移动构造函数,以确保每个资源实例都是唯一的。例如,std::unique_ptr
就是通过删除拷贝构造函数和拷贝赋值运算符来实现其独占所有权语义的。
6. 常见误解或错误 (Common Misunderstandings or Mistakes)
混淆
delete
关键字:- 误解: 认为
Point() = delete;
中的delete
和new
/delete
运算符中的delete
是一回事。 - 纠正: 两者完全不同。
new
/delete
是用于动态内存分配和释放的运算符。而= delete;
是一种函数声明语法,用于禁用特定的函数。它们在C++中的作用和语义是截然不同的。
- 误解: 认为
为已删除的函数提供实现:
- 误解: 在头文件声明
= delete;
后,仍然在.cpp
文件中提供了该函数的定义。 - 纠正: 这是错误的。一旦函数被标记为
= delete;
,就意味着它不应该被实现,编译器会因此报错。
- 误解: 在头文件声明
不理解删除构造函数的目的:
- 误解: 认为删除构造函数是为了“节省内存”或“优化性能”。
- 纠正: 删除构造函数的主要目的是为了控制对象创建行为和加强类设计语义。它与内存或性能优化通常没有直接关系(尽管可以间接帮助避免不必要的拷贝带来的性能开销,但这不是主要目的)。其核心在于强制执行特定的设计规则,例如确保资源独占或对象必须通过特定方式初始化。
过度删除导致可用性问题:
- 误解: 觉得所有构造函数都删除会更“安全”。
- 纠正: 如果删除了所有可用的构造函数,那么你的类将无法被实例化,这通常不是你想要的结果。你需要确保至少有一个构造函数是可用的,以便用户能够创建对象。同时,如果一个类没有显式声明拷贝构造函数和拷贝赋值运算符,C++会自动为其生成(如果满足条件)。如果你想阻止它们,你需要显式地
= delete;
它们。
忘记隐式生成规则:
- 误解: 认为只要不显式声明拷贝构造函数,就不会发生拷贝。
- 纠正: 如果你不显式声明拷贝构造函数,C++编译器会在需要时为你隐式生成一个默认的拷贝构造函数。要真正禁用拷贝,你必须显式地将其
= delete;
。对于移动构造函数和移动赋值运算符也是类似的。
7. 编程练习 (Coding Exercise)
现在,轮到你来动手实践了!
请根据以下要求,完善 Book
类的定义:
目标:
- 禁用默认构造函数: 确保每本书在创建时都必须提供书名和作者。
- 禁用拷贝构造函数: 确保
Book
对象不能被拷贝,每本书都是独特的。 - 允许通过书名和作者构造: 保持现有的带参数构造函数。
- 提供一个打印信息的方法。
Book.h
:
1 |
|
Book.cpp
:
1 |
|
main.cpp
:
1 |
|
请你完成 Book.h
中 TODO 1
和 TODO 2
的部分,然后运行代码,验证你的修改是否达到了预期的编译错误效果。完成后请将你的解决方案提供给我。
🧑💻 User
1 |
|
🤖 Assistant
太棒了!你的解决方案完全正确!
8. 解决方案评级与讲解 (Solution Rating and Explanation)
你的评级: 满分 (Excellent)!
你完美地完成了任务,正确地在 Book.h
文件中禁用了默认构造函数和拷贝构造函数。
实际答案和解释:
你的 Book.h
文件应该如下所示:
1 |
|
你的修改:
你把这两行代码:Book() = delete;
Book(const Book& other) = delete;
正确地放置在了 public
访问说明符下,并且使用了正确的 = delete;
语法。
为什么这是正确的?
Book() = delete;
: 通过这行代码,你明确告诉编译器,Book
类的无参数(默认)构造函数是被“删除”的。这意味着任何尝试通过Book myBook;
或Book myBook{};
这样的语法来创建Book
对象的代码,都会在编译时立即报错。这成功地强制了每本Book
都必须在创建时提供书名和作者。Book(const Book& other) = delete;
: 通过这行代码,你明确禁用了Book
类的拷贝构造函数。这意味着任何尝试通过Book book2 = book1;
或Book book2(book1);
这样的语法来拷贝Book
对象的代码,也会在编译时收到错误。这确保了每本书籍实例的独一无二性,防止了不必要的复制行为。
当你在 main.cpp
中尝试取消注释 Book book_default;
或 Book book_copy = book1;
时,你将得到清晰的编译错误,这正是我们想要达到的效果。
恭喜你!你已经掌握了 = delete;
的基本用法和重要性。
9. 进一步思考和扩展问题 (Suggestions for Extended Questions)
你已经学会了如何使用 = delete;
来禁用构造函数,这是一个非常强大的工具。为了更深入地理解这个概念,我建议你探索以下几个问题:
- 移动构造函数和移动赋值运算符的删除: 我们今天只删除了默认构造函数和拷贝构造函数。那么,C++11引入的移动构造函数 (
Move Constructor
) 和移动赋值运算符 (Move Assignment Operator
) 如何禁用?它们的删除通常意味着什么?(提示:回顾Point
类的例子中,我们也删除了移动构造函数)。 - 拷贝赋值运算符的删除: 除了构造函数,
= delete;
还能用来删除拷贝赋值运算符 (Copy Assignment Operator
),即operator=
。请思考一下,如果你希望一个类是不可拷贝的,除了删除拷贝构造函数,你还需要删除拷贝赋值运算符吗?为什么? - 通用函数删除:
= delete;
是否只能用于特殊的成员函数(如构造函数、析构函数、赋值运算符)?它能否用于删除任何普通的成员函数或者甚至非成员函数?如果可以,这有什么实际应用场景? = delete;
与private
的区别: 假设你想要禁用一个类的某个构造函数或赋值运算符。除了= delete;
,你是否可以将其声明为private
来达到类似的目的?这两种方法有什么区别?在什么情况下你会选择= delete;
而不是private
?(这是一个C++面试中非常常见的问题!)- 隐式生成的特殊成员函数: 如果一个类没有显式声明任何构造函数、析构函数、拷贝构造函数/赋值运算符、移动构造函数/赋值运算符,C++编译器会自动为它生成这些函数。了解这些隐式生成规则对于理解何时需要显式使用
= delete;
来禁用它们至关重要。你能查阅资料,总结一下C++中特殊成员函数的隐式生成规则吗?
这些问题将帮助你更全面地理解 = delete;
的用途以及C++中对象生命周期和资源管理的复杂性。期待你未来在C++学习中的进步!