类的quiz
99. quiz
1. 编译器会为C++的类自动生成什么函数?
编译器会为C++的类自动生成以下六种特殊的成员函数:
- 默认构造函数(如果没有定义任何构造函数)
- 析构函数(如果没有定义)
- 拷贝构造函数(如果没有定义并且某些条件满足)
- 拷贝赋值运算符(如果没有定义并且某些条件满足)
- 移动构造函数(如果没有定义并且某些条件满足,C++11及以后)
- 移动赋值运算符(如果没有定义并且某些条件满足,C++11及以后)
这些函数被称为特殊成员函数。它们是编译器为了支持一些基本操作(如初始化、拷贝、移动、销毁对象)而自动为类生成的。但是,如果你为类定义了自己的版本,编译器就不会再生成这些函数。
2. 什么条件下不会自动生成?
- 拷贝构造函数和拷贝赋值运算符
- 如果你没有显式定义它们,且类中的所有成员都可以被拷贝,编译器就会为你生成这两个函数。但是,如果类中有不能被拷贝的成员(例如,
std::unique_ptr
或者定义了删除的拷贝构造函数的类型的成员),编译器就不会生成这两个函数。
- 如果你没有显式定义它们,且类中的所有成员都可以被拷贝,编译器就会为你生成这两个函数。但是,如果类中有不能被拷贝的成员(例如,
- 移动构造函数和移动赋值运算符 从C++11开始,如果你没有显式定义它们,且类中的所有成员都可以被移动,编译器就会为你生成这两个函数。但是,如果类中有不能被移动的成员(例如,定义了删除的移动构造函数的类型的成员),或者你已经定义了拷贝构造函数、拷贝赋值运算符或析构函数,编译器就不会生成这两个函数。
这些规则的目的是为了保证对象的正确拷贝和移动,避免因为编程错误导致的问题,例如内存泄漏或者浅拷贝。
3. 这六种特殊成员函数的函数签名是什么?
这六种特殊成员函数的函数签名如下:
- 默认构造函数:
T()
- 析构函数:
~T()
- 拷贝构造函数:
T(const T& other)
- 拷贝赋值运算符:
T& operator=(const T& other)
- 移动构造函数:
T(T&& other)
- 移动赋值运算符:
T& operator=(T&& other)
其中,T
是类的名称,other
是另一个同类型的对象。在拷贝构造函数和拷贝赋值运算符中,other
是一个对常量的引用,表示这个函数不会修改other
。在移动构造函数和移动赋值运算符中,other
是一个右值引用,表示这个函数可能会修改other
,通常是通过“窃取”其资源。
4. 为什么这六种特殊的成员函数是这样子设定的?
-
默认构造函数:
T()
。这是最基本的构造函数,如果用户没有定义任何构造函数,编译器会提供一个。它不接受任何参数,用于创建类的一个新实例。 -
析构函数:
~T()
。这个函数在对象被销毁时调用,用于清理对象可能拥有的资源。如果用户没有定义,编译器会提供一个默认的析构函数。 -
拷贝构造函数:
T(const T& other)
。这个函数用于创建一个新对象,该对象是一个已存在对象的副本。const T&
表示这个函数接收一个对已存在对象的引用,且不会修改这个对象。 -
拷贝赋值运算符:
T& operator=(const T& other)
。这个函数用于将一个已存在对象的状态赋值给另一个对象。const T&
表示这个函数接收一个对已存在对象的引用,且不会修改这个对象。 -
移动构造函数:
T(T&& other)
。这个函数用于创建一个新对象,该对象“窃取”了一个已存在对象的资源。T&&
表示这个函数接收一个对已存在对象的右值引用,这个对象可能会被修改。 -
移动赋值运算符:
T& operator=(T&& other)
。这个函数用于将一个已存在对象的资源“窃取”给另一个对象。T&&
表示这个函数接收一个对已存在对象的右值引用,这个对象可能会被修改。
这些函数的设定方式主要是为了支持对象的创建、拷贝、移动和销毁,同时尽可能地避免不必要的对象复制和资源浪费。
简单来说,以拷贝构造函数为例,即使不使用const T& other
作为形参,而是使用T& other
也是可以声明拷贝构造函数的。但一般来说,我们需要override
编译器默认生成的拷贝构造函数,而且const T& other
会比T& other
好,因为使用const T& other
作为参数,可以从任何对象(包括const对象和临时对象)中拷贝。
临时对象(也称为右值)是不能绑定到非const的左值引用上的,但可以绑定到const的左值引用上。这是C++语言规则的一部分,主要是为了保护临时对象不被意外修改。
除此,如果不是&
引用类型的话,也会有问题。
-
性能:当参数是值时,会创建该对象的一个新的副本,这涉及到对象的复制操作,可能会消耗大量的时间和内存.特别是当对象较大时,复制的代价会非常高.而当参数是常量引用时,只需要传递引用,不需要复制对象,因此性能更高.
-
避免无限递归:如果拷贝构造函数的参数是值,那么在调用拷贝构造函数时,需要先创建一个新的对象,这又需要调用拷贝构造函数,从而形成无限递归,导致程序崩溃.而当参数是常量引用时,不会触发拷贝构造函数,因此可以避免无限递归.
所以,拷贝构造函数的参数通常是常量引用,而不是值.
5. 为什么移动构造函数一般情况下会比拷贝构造函数快?
6. 移动的时候如果不将原来的置空会怎样?
在编程中,”移动”通常指的是移动语义,这是C++11引入的一个新特性。移动语义允许资源(如动态分配的内存)从一个对象转移到另一个对象,这样可以避免不必要的临时对象的复制。
如果在移动操作后不将原对象置空,可能会导致一些问题。例如,如果原对象是一个拥有动态分配内存的对象,那么当原对象和新对象都试图释放同一块内存时,就会发生双重删除,这会导致程序崩溃。
因此,通常在执行移动操作后,我们会将原对象置空或设置为一个安全的状态,以防止这种问题的发生。
7. 一个类B,它有一个A*类型的成员变量.并写出它的拷贝构造函数.
class A {
// A的定义
};
class B {
public:
B() : ptr(new A()) {} // 默认构造函数
B(const B& other) : ptr(new A(*(other.ptr))) {} // 拷贝构造函数
~B() { delete ptr; } // 析构函数
private:
A* ptr; // A*类型的成员变量
};