Effective C++ 阅读心得-条款06:若不想使用编译器自动生成的函数,就该明确拒绝

文章讨论了如何防止编译器为类自动生成默认函数,如通过声明为private且不实现,或使用C++11的delete关键字。特别提到了单例模式,强调了禁用拷贝构造函数和赋值运算符的重要性,并介绍了Uncopyable基类和unique_ptr的使用,后者通过delete关键字直接禁用拷贝行为。
摘要由CSDN通过智能技术生成

重要的话放在前面

1. 如果你不希望哪些编译器为类提供的缺省函数被使用,那么请将其声明为private并不予实现

  • 条款05中提到,编译器会为类提供缺省的函数,比如构造函数,析构函数,拷贝构造,赋值操作符等。那如果你并不希望编译器提供的缺省函数被用户所使用,那么你可以将其声明出来,并且将其声明为private,但并不定义。
  • C++11另外提供了两个关键字,分别是defaultdelete,用于声明函数的声明和禁用,其中delete也可以实现上述的功能,并且更加安全有效。
  • 为什么delete关键字比将函数声明为private且不实现更加有效。
  1. 在之前的C++标准中,为了禁用类中的函数,一般采用将函数声明为private,并且不提供函数实现的方式,如果这样,在程序编写时,你可能不小心调用到这个函数,但在编译期编译器并不会发现有什么问题,而是会在连接期告诉你一个连接错误
  2. 使用delete关键字禁用函数,编译器会在编译时检查函数是否被删除,更加高效。

1.1 将想禁用的函数设置为private并不提供实现

  • 让我们来看看面试经典:单例模式
  • 单例模式是一种创建型设计模式,它确保类只有一个实例,并且提供全局访问点。
  • 单例模式的特点
    1. 一个类只有一个实例,该实例被全局访问
    2. 该实例必须由该类自行创建,不允许外部直接创建
    3. 该类必须提供一种全局访问方式,让外界能够访问该类的唯一实例
  • 单例模式的应用场景
    1. 需要有一个类只能存在一个实例的场景,如系统中的配置信息、日志记录器等
    2. 需要频繁创建和销毁的对象,如线程池、数据库连接池等
    3. 为了节省系统资源,需要限制类的实例数量的场景
  • 单例模式的设计思路
    1. 懒汉式(Lazy Initialization):在第一次访问时才创建唯一的实例。
    2. 饿汉式(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。

打完收工!都看到这了,点个赞再走呗~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值