重要的话放在前面
- 为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为
private并
且不予实现。使用像uncopyable
这样的base class
也是一种做法。
1. 如果你不希望哪些编译器为类提供的缺省函数被使用,那么请将其声明为private
并不予实现
- 条款05中提到,编译器会为类提供缺省的函数,比如构造函数,析构函数,拷贝构造,赋值操作符等。那如果你并不希望编译器提供的缺省函数被用户所使用,那么你可以将其声明出来,并且将其声明为
private
,但并不定义。 - C++11另外提供了两个关键字,分别是
default
和delete
,用于声明函数的声明和禁用,其中delete
也可以实现上述的功能,并且更加安全有效。 - 为什么
delete
关键字比将函数声明为private
且不实现更加有效。
- 在之前的C++标准中,为了禁用类中的函数,一般采用将函数声明为
private
,并且不提供函数实现的方式,如果这样,在程序编写时,你可能不小心调用到这个函数,但在编译期编译器并不会发现有什么问题,而是会在连接期告诉你一个连接错误。 - 使用delete关键字禁用函数,编译器会在编译时检查函数是否被删除,更加高效。
1.1 将想禁用的函数设置为private
并不提供实现
- 让我们来看看面试经典:单例模式
- 单例模式是一种创建型设计模式,它确保类只有一个实例,并且提供全局访问点。
- 单例模式的特点
- 一个类只有一个实例,该实例被全局访问
- 该实例必须由该类自行创建,不允许外部直接创建
- 该类必须提供一种全局访问方式,让外界能够访问该类的唯一实例
- 单例模式的应用场景
- 需要有一个类只能存在一个实例的场景,如系统中的配置信息、日志记录器等
- 需要频繁创建和销毁的对象,如线程池、数据库连接池等
- 为了节省系统资源,需要限制类的实例数量的场景
- 单例模式的设计思路
- 懒汉式(Lazy Initialization):在第一次访问时才创建唯一的实例。
- 饿汉式(Eager Initialization):在类被加载时就创建唯一的实例。
- 注意事项:无论是懒汉式还是饿汉式,都需要将构造函数声明为私有的,以禁止外部实例化该类,并通过静态方法或者静态成员变量实现访问该类唯一实例的方法。需要注意的是,为了保证单例的唯一性,应该禁止拷贝构造函数和赋值运算符的调用。
- 下面提供简单好用的饿汉式单例模式的实现方式:
class Singleton {
private:
static Singleton* instance; //静态成员变量
Singleton() {} // 构造函数私有化
Singleton(const Singleton&); // 禁用拷贝构造函数
Singleton& operator=(const Singleton&); // 禁用赋值运算符
public:
static Singleton* getInstance() {
return instance;
}
void someMethod() {
// 实现一些方法
}
};
Singleton* Singleton::instance = new Singleton(); // 在类外部进行初始化
- 在这个实现中,将拷贝构造函数和赋值运算符都声明为
private
并且不提供实现,这样就可以有效禁用它们的调用。 - 同时由于饿汉式单例在程序运行期间就已经创建了唯一实例,因此在获取实例时只需要返回该实例指针即可,且不需要调用拷贝构造函数或赋值运算符。
- 如前所说,如果这个单例有友元类,这个友元类不知天高地厚,想调用这个单例的拷贝构造函数,那么它将收到一个连接错误。
- 出现连接错误的问题是没有在编译期出现错误来的快,也就是编写程序的时候编译器并不会告诉你哪里错了,而编译期是在编写程序时,编译器就会告诉你。下面看看书中给的方法,将连接错误提前至编译期。
1.2 将连接错误移至编译期
class Uncopyable {
protected: //允许derived对象构造和析构
Uncopyable() { }
~Uncopyable() { }
private:
Uncopyable(const Uncopyable&); //但阻止copying
Uncopyable& operator=(const Uncopyable&) ;
};
为求阻止Singleton
对象被拷贝,我们唯一需要做的就是继承Uncopyable
:
class Singleton: private Uncopyable {
private:
static Singleton* instance; //静态成员变量
Singleton() {} // 构造函数私有化
//此处不再声明 copy 构造函数和 copy assign 操作符,
//因为调用默认的 copy 构造函数和 copy assign 操作符将默认调用父类的 copy 构造函数和 copy assign 操作符
public:
static Singleton* getInstance() {
return instance;
}
void someMethod() {
// 实现一些方法
}
};
Singleton* Singleton::instance = new Singleton(); // 在类外部进行初始化
- 此时如果
Singleton
有成员函数或者友元函数来访问Singleton
的缺省拷贝构造函数或赋值操作符,将直接在编译器得到一个编译错误。将错误时期提前,提高了程序编写的效率。 - 但这么做是否会有些麻烦呢?一直扮演
base class
的角色,程序是否会出现多重继承的问题? - 那看看C++11给我们带来了什么惊喜呢?
1.3 使用delete
禁用拷贝构造和拷贝赋值运算符的面试经典例子: unique_ptr
unique_ptr
是C++11中引入的一种智能指针,用于管理动态内存资源。- 它的设计思路是在一个独占所有权的语义下管理内存资源,即一个
unique_ptr
对象可以拥有某一块内存资源的唯一所有权,如果需要传递这个资源,只能通过移动语义转移所有权,而不能进行复制操作。 - 这种独占所有权的特性使得
unique_ptr
可以非常方便地管理动态内存资源,避免内存泄漏和多次释放同一块内存的问题。 - 在实现上,unique_ptr是一个类模板,定义如下:
template<typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
public:
// 构造函数
explicit unique_ptr(T* ptr = nullptr, Deleter deleter = Deleter());
// 禁止拷贝和赋值
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
// 移动构造函数和移动赋值运算符
unique_ptr(unique_ptr&& other);
unique_ptr& operator=(unique_ptr&& other);
// 释放资源的函数
void reset(T* ptr = nullptr);
T* release();
// 访问被管理的指针的函数
T* get() const;
// 重载*和->运算符
T& operator*() const;
T* operator->() const;
// 析构函数
~unique_ptr();
private:
T* ptr_;
Deleter deleter_;
};
- 从定义可以看出,
unique_ptr
包含了构造函数、移动构造函数、移动赋值运算符、reset
函数、release
函数、get
函数、*
运算符和->
运算符等重要成员函数,以及析构函数。其中,禁止拷贝和赋值操作是通过将拷贝构造函数和赋值运算符设为删除状态来实现的。 - 这里的禁用是直接使用
delete
修饰符设置为删除状态,此时不需要编写并继承Uncopyable
类就直接可以在编译期获取到错误信息,是不是很方便?很nice。
打完收工!都看到这了,点个赞再走呗~