当前位置: 首页 > article >正文

C++ 中的默认删除特征:管理资源与防止意外拷贝

C++ 中的默认删除特征:管理资源与防止意外拷贝

在 C++ 中,默认删除特殊成员函数(如拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符)是一种重要的设计实践,用于确保类的行为符合预期,特别是在管理独占资源或设计单例类时。本文将深入探讨默认删除的概念、其重要性、应用场景以及如何正确使用。

什么是默认删除?

C++11 引入了 = delete 语法,允许程序员明确指示编译器某个成员函数不应被调用。对于类的特殊成员函数,如果它们不需要或不适合被调用,可以通过 = delete 来禁止其生成或调用。这种机制被称为“默认删除”。

为什么要默认删除?

避免意外拷贝

当一个类管理着如文件句柄、网络连接或独占资源等时,拷贝构造函数和拷贝赋值运算符的调用可能导致资源管理问题。例如,如果两个对象共享同一个资源,可能会导致资源被错误地释放或访问,从而引发程序崩溃或数据损坏。通过删除这些函数,可以防止此类问题的发生。

保证单例性

单例模式要求一个类只有一个实例,并提供一个全局访问点。如果允许拷贝或赋值操作,就可能创建出多个实例,破坏单例性。通过删除拷贝构造函数和拷贝赋值运算符,可以确保类的单例性。

性能优化

对于包含大量数据或复杂成员的对象,拷贝或移动操作可能非常耗时。通过删除这些函数,可以避免不必要的性能开销,尤其是在高性能要求的场景中。

安全性

删除拷贝构造函数和拷贝赋值运算符可以确保类的使用者不会因意外调用这些函数而导致错误的行为。这有助于提高代码的安全性和可维护性。

示例

假设我们有一个 MyClass 类,它管理一个文件描述符。我们不希望 MyClass 的对象被拷贝,因为拷贝会导致资源被多个对象共享,这是不可取的。

#include <iostream>
#include <fcntl.h>  // For open/close
#include <unistd.h> // For close

class MyClass {
private:
    int fileDescriptor;

public:
    MyClass() : fileDescriptor(open("/path/to/file", O_RDWR|O_CREAT, 0644)) {
        if (fileDescriptor == -1) {
            throw std::runtime_error("Failed to open file");
        }
    }

    ~MyClass() {
        if (fileDescriptor != -1) {
            close(fileDescriptor);
        }
    }

    MyClass(const MyClass&) = delete; // 删除拷贝构造函数
    MyClass& operator=(const MyClass&) = delete; // 删除拷贝赋值运算符

    // 移动构造函数和移动赋值运算符(如果需要)...
};

int main() {
    MyClass obj1;
    // 下面的代码会编译失败,因为拷贝构造函数被删除了
    // MyClass obj2 = obj1; // 错误:拷贝构造函数被删除

    return 0;
}

在这个例子中,通过删除拷贝构造函数和拷贝赋值运算符,我们确保了 MyClass 的对象不能被拷贝,从而保证了资源的独占性。

父类与子类中的默认删除

在类继承中,如果父类的拷贝构造函数或拷贝赋值运算符被删除,子类可以有自己的拷贝构造函数和拷贝赋值运算符,但这些函数在实现时需要特别注意。子类的拷贝构造函数或拷贝赋值运算符需要显式地调用父类的构造函数(尽管父类的拷贝构造函数已被删除),但这里的调用实际上是通过其他方式实现的(如使用默认构造函数或移动构造函数)。

然而,如果子类需要拷贝父类的状态,并且父类的拷贝构造函数被删除了,那么子类将无法实现自己的拷贝构造函数,因为编译器无法为子类生成一个合法的拷贝构造函数来调用父类的被删除拷贝构造函数。

示例:父类与子类中的默认删除

假设我们有一个父类 Base 和一个子类 Derived,其中父类的拷贝构造函数和拷贝赋值运算符被删除了。

#include <iostream>

// 父类 Base
class Base {
public:
    Base() { std::cout << "Base constructor called\n"; }
    Base(const Base&) = delete; // 删除拷贝构造函数
    Base& operator=(const Base&) = delete; // 删除拷贝赋值运算符
    virtual ~Base() { std::cout << "Base destructor called\n"; }
};

// 子类 Derived
class Derived : public Base {
public:
    Derived() { std::cout << "Derived constructor called\n"; }
    Derived(const Derived& other) : Base(other) { std::cout << "Derived copy constructor called\n"; }
    Derived& operator=(const Derived& other) { std::cout << "Derived copy assignment operator called\n"; return *this; }
    ~Derived() { std::cout << "Derived destructor called\n"; }
};

int main() {
    Derived d1;
    // 下面的代码会编译失败,因为无法调用 Base 的拷贝构造函数
    // Derived d2 = d1; // 错误:无法调用 Base 的拷贝构造函数

    return 0;
}

在这个例子中,尝试在子类 Derived 的拷贝构造函数中调用父类 Base 的拷贝构造函数会导致编译错误,因为 Base 的拷贝构造函数被删除了。

总结

默认删除特殊成员函数是 C++ 中一种强大的特性,它允许程序员明确控制类的行为,防止对象被意外拷贝或移动,从而避免资源管理问题、保证单例性、优化性能和提高代码安全性。在设计类时,特别是管理资源或实现特定设计模式的类时,应该考虑是否需要默认删除这些特殊成员函数。同时,在类继承中,也需要注意父类中默认删除的构造函数对子类的影响。通过正确使用默认删除,可以编写出更加安全、高效和可靠的代码。


http://www.kler.cn/news/302700.html

相关文章:

  • 【通俗理解】二项分布的均值与方差——从成功与失败的概率看分布
  • python如何加速计算密集型任务2?
  • 【C#】DrawCurve的用法
  • 【C++开发中使用JSON的妙用】
  • JVM——Java虚拟机内存初识(面经篇之JVM内存)
  • Gin 自带日志系统:深入理解与自定义
  • 【VUE】实现当前页面刷新,刷新当前页面的两个方法(如何在一个页面写一个方法提供给全局其他地方调用)(如何重复调用同一个路由实现页面的重新加载)
  • Unity 给模型贴上照片
  • 软件测试工程师面试整理-测试类型
  • vue+IntersectionObserver + scrollIntoView 实现电梯导航
  • 【机器人建模和控制】读书笔记
  • 中秋节了,送大家一个月饼
  • 浙大数据结构:04-树5 Root of AVL Tree
  • 【C++\Qt项目实战】俄罗斯方块
  • iOS 15推出后利用邮件打开率的7种方法
  • 向量空间与函数空间的类比分析
  • Linux入门攻坚——32、Mini Linux制作
  • oracle 条件取反
  • Elasticsearch介绍以及solr对比
  • 高级java每日一道面试题-2024年9月03日-JVM篇-怎么判断对象是否可以被回收?
  • MySql-表的内外连接
  • QLable提升类
  • python画图|3D垂线标记
  • 九、Redis 的实际使用与Redis的设计
  • Android Auto助力电动汽车智能驾驶
  • Java面试篇基础部分-Java各种垃圾收集器
  • 电脑提示丢失mfc140u.dll的详细解决方案,mfc140u.dll文件是什么
  • DAY99 APP 攻防-小程序篇反编译外在抓包主包分包配置泄漏算法逆向未授权
  • VS code 写下 print 时让编译器自动添加括号
  • 第二百二十五节 JPA教程 - JPA列长度示例、JPA列精度范围示例