☰
  • 首页
  • 规则分类
  • 项目介绍
search
•••

构造函数抛出异常需避免相关资源泄漏

2.11 ID_throwInConstructor
目录 › next › previous

如果构造函数抛出异常,对应的析构函数将不会被执行,因此应保证已分配的资源能够被有效回收。

示例:

class A {
    int *a, *b;

public:
    A(size_t n):
        a(new int[n]),
        b(new int[n])     // The allocations may fail
    {
        if (sth_wrong) {
            throw E();    // User exceptions
        }
    }

   ~A() {                 // May be invalid
        delete[] a;
        delete[] b;
    }
};

例中内存分配可能会失败,抛出 bad_alloc 异常,在某种条件下还会抛出自定义的异常,任何一种异常被抛出析构函数就不会被执行,已分配的资源就无法被回收,但已构造完毕的基类对象和成员对象还是会正常析构的,所以应采用对象化资源管理方法,使资源可以被自动回收,可参见 ID_ownerlessResource 的进一步讨论。

可改为:

A::A(size_t n) {
    // Use objects to hold resources
    auto holder_a = make_unique<int[]>(n);
    auto holder_b = make_unique<int[]>(n);

    // Do the tasks that may throw exceptions
    if (sth_wrong) {
        throw E();
    }

    // Transfer ownership, make sure no exception is thrown
    a = holder_a.release();
    b = holder_b.release();
}

先用 unique_ptr 对象持有资源,完成可能抛出异常的事务之后,再将资源转移给相关成员,转移的过程不可抛出异常,这种模式可以保证异常安全,如果有异常抛出,资源均可被正常回收。对遵循 C++11 及之后标准的代码,建议用 make_unique 等工厂函数代替 new 运算符。

示例代码意在讨论一种通用模式,实际代码可采用更直接的方式:

class A {
    vector<int> a, b;  // Or use ‘unique_ptr’

public:
    A(size_t n): a(n), b(n) {  // Safe and brief
        ....
    }
};

保证已分配的资源时刻有对象负责回收是重要的设计原则。

注意,“未成功初始化的对象”在 C++ 语言中是不存在的,应避免相关逻辑错误,如:

struct T {
    A() { throw CtorException(); }
};

void foo() {
    T* p = nullptr;
    try {
        p = new T;
    }
    catch (CtorException&) {
        delete p;              // Logic error, ‘p’ is nullptr
        return;
    }
    ....
    delete p;
}

例中 T 类型的对象在构造时抛出异常,而实际上 p 并不会指向一个未能成功初始化的对象,赋值被异常中断,catch 中的 p 仍然是一个空指针,new 表达式中抛出异常会自动回收已分配的内存。

相关

ID_ownerlessResource ID_multiAllocation ID_memoryLeak

依据

ISO/IEC 14882:2003 5.3.4(17) ISO/IEC 14882:2011 5.3.4(18) ISO/IEC 14882:2017 8.3.4(21)
Copyright©2024 360 Security Technology Inc., Licensed under the Apache-2.0 license.