C++析构函数中抛出异常问题详解与解决方案

C++析构函数异常处理解析

C++析构函数中抛出异常问题详解与解决方案

一、问题概述

析构函数中抛出异常是C++编程中极其危险的行为,主要问题包括:

  1. 程序终止:如果在栈展开过程中析构函数抛出异常,程序会调用std::terminate()
  2. 资源泄漏:异常中断资源释放过程
  3. 未定义行为:破坏异常处理机制
  4. 调试困难:异常信息丢失,难以追踪根本原因

二、C++标准规定

2.1 标准规则

根据C++标准(§15.2/3):

  • 如果异常正在传播时,从析构函数抛出第二个异常,程序将终止
  • 析构函数默认标记为noexcept(true)(C++11起)
class Resource {
public:
    ~Resource() {
        // C++11起:隐式声明为 noexcept
        // 如果这里抛出异常,程序可能终止
    }
};

三、问题场景分析

3.1 栈展开过程中的异常

// ❌ 危险示例:析构函数在栈展开中抛出异常
#include <iostream>
#include <stdexcept>

class DangerousResource {
public:
    DangerousResource(const char* name) : name_(name) {
        std::cout << "Acquiring: " << name_ << std::endl;
    }
    
    ~DangerousResource() {
        std::cout << "Releasing: " << name_ << std::endl;
        // 危险:可能在栈展开时抛出异常
        if (std::uncaught_exceptions() > 0) {
            std::cout << "WARNING: Exception already active!" << std::endl;
        }
        throw std::runtime_error("Error in ~DangerousResource()");
    }
    
private:
    const char* name_;
};

void risky_function() {
    DangerousResource res1("Resource1");
    DangerousResource res2("Resource2");
    throw std::runtime_error("Primary exception");
}

int main() {
    try {
        risky_function();
    } catch (const std::exception& e) {
        std::cout << "Caught: " << e.what() << std::endl;
        // 实际上程序可能在析构函数中终止
    }
    return 0;
}
// 输出可能:
// Acquiring: Resource1
// Acquiring: Resource2
// Releasing: Resource2
// terminate called after throwing an instance of 'std::runtime_error'
// 程序终止!

3.2 容器元素析构异常

// ❌ 危险示例:容器中的对象析构抛出异常
#include <vector>
#include <iostream>
#include <memory>

class Element {
public:
    Element(int id) : id_(id) {
        std::cout << "Element " << id_ << " created" << std::endl;
    }
    
    ~Element() noexcept(false) {  // 错误地关闭noexcept
        std::cout << "Element " << id_ << " destroying" << std::endl;
        if (id_ == 2) {
            throw std::runtime_error("Failed to destroy element 2");
        }
    }
    
private:
    int id_;
};

int main() {
    try {
        std::vector<Element> elements;
        elements.reserve(5);
        
        for (int i = 0; i < 5; ++i) {
            elements.emplace_back(i);
        }
        
        // vector析构时会逐个销毁元素
        // 当元素2抛出异常时,其他元素可能不会被正确销毁
    } catch (const std::exception& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }
    return 0;
}
// 问题:元素3、4、5可能泄漏资源

3.3 继承体系中的析构函数

// ❌ 危险示例:基类和派生类析构函数都可能抛出异常
#include <iostream>
#include <memory>

class Base {
public:
    virtual ~Base() {
        std::cout << "Base destructor" << std::endl;
        cleanup_base();
    }
    
    virtual void cleanup_base() {
        // 可能抛出异常
        throw std::runtime_error("Base cleanup failed");
    }
};

class Derived : public Base {
public:
    ~Derived() override {
        std::cout << "Derived destructor" << std::endl;
        cleanup_derived();
    }
    
    void cleanup_derived() {
        // 也可能抛出异常
        throw std::runtime_error("Derived cleanup failed");
    }
    
    void cleanup_base() override {
        // 重写基类清理
        std::cout << "Derived cleaning up base resources" << std::endl;
        // 仍然可能抛出异常
    }
};

int main() {
    try {
        Derived d;
        // d离开作用域时,会先调用~Derived(),再调用~Base()
        // 如果两者都抛出异常,程序终止
    } catch (const std::exception& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }
    return 0;
}

四、解决方案

4.1 基本原则:不在析构函数中抛出异常

4.1.1 使用noexcept确保安全
// ✅ 正确示例:使用noexcept确保析构函数安全
#include <iostream>
#include <fstream>

class SafeFile {
private:
    std::FILE* file_;
    
public:
    explicit SafeFile(const char* filename) 
        : file_(std::fopen(filename, "r")) {
        if (!file_) {
            throw std::runtime_error("Failed to open file");
        }
        std::cout << "File opened: " << filename << std::endl;
    }
    
    // 声明为noexcept,承诺不抛出异常
    ~SafeFile() noexcept {
        std::cout << "Closing file" << std::endl;
        if (file_) {
            // 即使失败也不抛出异常
            std::fclose(file_);
            // 可以记录日志,但不抛出
        }
    }
    
    // 禁止拷贝
    SafeFile(const SafeFile&) = delete;
    SafeFile& operator=(const SafeFile&) = delete;
    
    // 允许移动
    SafeFile(SafeFile&& other) noexcept 
        : file_(other.file_) {
        other.file_ = nullptr;
    }
    
    SafeFile& operator=(SafeFile&& other) noexcept {
        if (this != &other) {
            cleanup();
            file_ = other.file_;
            other.file_ = nullptr;
        }
        return *this;
    }
    
    void read_data() {
        // 使用文件...
    }
    
private:
    void cleanup() noexcept {
        if (file_) {
            std::fclose(file_);
            file_ = nullptr;
        }
    }
};

int main() {
    try {
        SafeFile file("data.txt");
        file.read_data();
        // 析构函数被调用,保证不抛出异常
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    return 0;
}
4.1.2 提供显式释放函数
// ✅ 正确示例:提供显式释放函数,析构函数作为后备
#include <iostream>
#include <memory>
#include <mutex>

class ThreadSafeResource {
private:
    std::unique_ptr<int[]> data_;
    std::mutex mutex_;
    bool released_;
    
public:
    ThreadSafeResource(size_t size) 
        : data_(std::make_unique<int[]>(size)), 
          released_(false) {
        std::cout << "Resource allocated" << std::endl;
    }
    
    // 显式释放函数,可以抛出异常
    void release() {
        std::lock_guard<std::mutex> lock(mutex_);
        
        if (released_) {
            return; // 已经释放
        }
        
        std::cout << "Releasing resource..." << std::endl;
        
        // 可能抛出异常的清理操作
        perform_complex_cleanup();
        
        data_.reset();
        released_ = true;
        std::cout << "Resource released successfully" << std::endl;
    }
    
    // 析构函数作为后备,不抛出异常
    ~ThreadSafeResource() noexcept {
        try {
            if (!released_) {
                std::cout << "Warning: Resource not explicitly released, "
                          << "performing emergency cleanup" << std::endl;
                
                std::lock_guard<std::mutex> lock(mutex_);
                if (!released_) {
                    // 执行简单的、不会抛出异常的清理
                    data_.reset();
                    released_ = true;
                }
            }
        } catch (...) {
            // 捕获所有异常,防止传播
            std::cerr << "ERROR: Unexpected exception in destructor" << std::endl;
            // 记录日志,但不重新抛出
        }
    }
    
    // 使用资源
    void process() {
        std::lock_guard<std::mutex> lock(mutex_);
        if (released_) {
            throw std::runtime_error("Resource already released");
        }
        // 处理数据...
    }
    
private:
    void perform_complex_cleanup() {
        // 复杂的清理逻辑,可能失败
        // 但可以抛出异常,因为不是在析构函数中
        if (data_) {
            // 模拟可能失败的操作
            bool cleanup_failed = false; // 模拟变量
            
            if (cleanup_failed) {
                throw std::runtime_error("Complex cleanup failed");
            }
        }
    }
};

int main() {
    try {
        ThreadSafeResource resource(1024);
        
        resource.process();
        
        // 显式释放,可以处理异常
        resource.release();
        
    } catch (const std::exception& e) {
        std::cerr << "Error during operation: " << e.what() << std::endl;
        // 即使release()抛出异常,析构函数仍会安全清理
    }
    
    return 0;
}

4.2 异常安全的资源管理

4.2.1 RAII模式与异常安全
// ✅ 正确示例:异常安全的RAII包装器
#include <iostream>
#include <memory>
#include <functional>
#include <vector>

// 通用RAII包装器
template<typename Resource, typename Cleanup>
class ScopedResource {
private:
    Resource resource_;
    Cleanup cleanup_;
    bool released_;
    
public:
    // 构造函数可能抛出异常
    template<typename... Args>
    ScopedResource(Cleanup cleanup, Args&&... args) 
        : resource_(std::forward<Args>(args)...), 
          cleanup_(std::move(cleanup)), 
          released_(false) {
    }
    
    // 移动构造
    ScopedResource(ScopedResource&& other) noexcept
        : resource_(std::move(other.resource_)),
          cleanup_(std::move(other.cleanup_)),
          released_(other.released_) {
        other.released_ = true;
    }
    
    // 析构函数不抛出异常
    ~ScopedResource() noexcept {
        if (!released_) {
            try {
                cleanup_(resource_);
            } catch (...) {
                // 记录错误但不抛出
                std::cerr << "WARNING: Cleanup threw exception, ignoring" << std::endl;
            }
        }
    }
    
    // 显式释放
    void release() {
        if (!released_) {
            cleanup_(resource_);
            released_ = true;
        }
    }
    
    // 获取资源
    Resource& get() { return resource_; }
    const Resource& get() const { return resource_; }
    
    // 禁止拷贝
    ScopedResource(const ScopedResource&) = delete;
    ScopedResource& operator=(const ScopedResource&) = delete;
};

// 使用示例
void demo_scoped_resource() {
    // 示例1:文件句柄
    auto file_cleanup = [](FILE* f) {
        if (f && f != stdin && f != stdout && f != stderr) {
            std::cout << "Closing file" << std::endl;
            if (std::fclose(f) != 0) {
                // 记录错误但不抛出
                std::cerr << "Failed to close file" << std::endl;
            }
        }
    };
    
    {
        ScopedResource<FILE*, decltype(file_cleanup)> 
            file(file_cleanup, std::fopen("test.txt", "w"));
        
        if (file.get()) {
            std::fprintf(file.get(), "Hello World\n");
        }
        // 文件自动关闭,即使抛出异常
    }
    
    // 示例2:内存分配
    auto memory_cleanup = [](int* ptr) {
        std::cout << "Freeing memory" << std::endl;
        delete[] ptr;
    };
    
    ScopedResource<int*, decltype(memory_cleanup)> 
        memory(memory_cleanup, new int[100]);
    
    // 使用内存...
    memory.get()[0] = 42;
}
4.2.2 使用std::unique_ptr自定义删除器
// ✅ 正确示例:使用unique_ptr自定义删除器
#include <iostream>
#include <memory>
#include <vector>

// 自定义删除器,保证不抛出异常
struct SafeFileDeleter {
    void operator()(FILE* f) const noexcept {
        if (f && f != stdin && f != stdout && f != stderr) {
            std::cout << "Deleter closing file" << std::endl;
            // 忽略错误,保证不抛出
            std::fclose(f);
        }
    }
};

using SafeFilePtr = std::unique_ptr<FILE, SafeFileDeleter>;

// 复杂资源的自定义删除器
class DatabaseConnection {
public:
    static DatabaseConnection* connect(const std::string& url) {
        std::cout << "Connecting to database: " << url << std::endl;
        return new DatabaseConnection();
    }
    
    void disconnect() {
        std::cout << "Disconnecting database" << std::endl;
        // 可能抛出异常的操作
        throw std::runtime_error("Disconnection failed");
    }
    
    void query(const std::string& sql) {
        std::cout << "Executing: " << sql << std::endl;
    }
};

struct SafeDatabaseDeleter {
    void operator()(DatabaseConnection* db) const noexcept {
        if (db) {
            try {
                db->disconnect();  // 可能抛出异常
            } catch (const std::exception& e) {
                // 记录但不传播异常
                std::cerr << "Database disconnection error: " 
                          << e.what() << std::endl;
            } catch (...) {
                std::cerr << "Unknown database disconnection error" << std::endl;
            }
            delete db;
        }
    }
};

using SafeDatabasePtr = std::unique_ptr<DatabaseConnection, SafeDatabaseDeleter>;

int main() {
    // 文件示例
    SafeFilePtr file(std::fopen("data.txt", "w"));
    if (file) {
        std::fprintf(file.get(), "Test data\n");
    }
    
    // 数据库示例
    try {
        SafeDatabasePtr db(DatabaseConnection::connect("localhost/test"));
        db->query("SELECT * FROM users");
        
        // 即使后面抛出异常,删除器也会安全清理
        throw std::runtime_error("Simulated error");
        
    } catch (const std::exception& e) {
        std::cout << "Caught: " << e.what() << std::endl;
        // db的删除器会被调用,不会导致程序终止
    }
    
    return 0;
}

4.3 处理多重资源清理

4.3.1 资源收集器模式
// ✅ 正确示例:资源收集器模式
#include <iostream>
#include <vector>
#include <functional>
#include <memory>

class ResourceCollector {
private:
    std::vector<std::function<void()>> cleanup_actions_;
    bool committed_;
    
public:
    ResourceCollector() : committed_(false) {}
    
    ~ResourceCollector() noexcept {
        rollback();
    }
    
    // 添加清理动作
    template<typename Fn>
    void add_cleanup(Fn&& cleanup) {
        cleanup_actions_.emplace_back(std::forward<Fn>(cleanup));
    }
    
    // 提交所有资源(不执行清理)
    void commit() noexcept {
        committed_ = true;
        cleanup_actions_.clear();
    }
    
    // 回滚(执行所有清理)
    void rollback() noexcept {
        if (committed_) {
            return;
        }
        
        // 逆序执行清理(LIFO)
        for (auto it = cleanup_actions_.rbegin(); 
             it != cleanup_actions_.rend(); ++it) {
            try {
                (*it)();
            } catch (const std::exception& e) {
                std::cerr << "Cleanup error (ignored): " << e.what() << std::endl;
            } catch (...) {
                std::cerr << "Unknown cleanup error (ignored)" << std::endl;
            }
        }
        
        cleanup_actions_.clear();
        committed_ = true;
    }
};

// 使用示例
class TransactionalFile {
private:
    FILE* file_;
    ResourceCollector& collector_;
    
public:
    TransactionalFile(const char* filename, 
                     ResourceCollector& collector)
        : collector_(collector) {
        
        file_ = std::fopen(filename, "w");
        if (!file_) {
            throw std::runtime_error("Failed to open file");
        }
        
        // 注册清理动作
        collector_.add_cleanup([this]() {
            if (file_) {
                std::cout << "Rollback: closing file" << std::endl;
                std::fclose(file_);
                file_ = nullptr;
            }
        });
    }
    
    ~TransactionalFile() {
        // 析构函数不做清理,由ResourceCollector管理
    }
    
    void write(const char* data) {
        if (!file_) {
            throw std::runtime_error("File not open");
        }
        std::fprintf(file_, "%s", data);
    }
};

void perform_transaction() {
    ResourceCollector collector;
    
    try {
        TransactionalFile file1("file1.txt", collector);
        TransactionalFile file2("file2.txt", collector);
        
        file1.write("Data for file1\n");
        file2.write("Data for file2\n");
        
        // 模拟错误
        throw std::runtime_error("Transaction failed");
        
        // 如果成功到达这里
        collector.commit();
        std::cout << "Transaction committed" << std::endl;
        
    } catch (const std::exception& e) {
        std::cerr << "Transaction error: " << e.what() << std::endl;
        // collector的析构函数会自动回滚
        // 或者可以显式调用:collector.rollback();
    }
    
    // 离开作用域时,如果未提交,collector会回滚
}

int main() {
    perform_transaction();
    return 0;
}

4.4 处理继承体系中的析构函数

4.4.1 虚析构函数的异常安全
// ✅ 正确示例:安全的虚析构函数实现
#include <iostream>
#include <memory>
#include <mutex>

class Base {
public:
    virtual ~Base() noexcept {
        // 基类析构函数必须处理异常安全
        try {
            cleanup();
        } catch (...) {
            handle_destructor_exception();
        }
    }
    
    // 显式释放函数
    virtual void release() {
        cleanup();
    }
    
protected:
    virtual void cleanup() {
        // 基类清理逻辑
        std::cout << "Base cleanup" << std::endl;
    }
    
private:
    void handle_destructor_exception() noexcept {
        // 记录错误,保证不抛出
        std::cerr << "CRITICAL: Exception in base destructor" << std::endl;
        // 可能的紧急清理
    }
};

class Derived : public Base {
protected:
    void cleanup() override {
        // 先清理派生类资源
        cleanup_derived();
        // 再调用基类清理
        Base::cleanup();
    }
    
private:
    void cleanup_derived() {
        std::cout << "Derived cleanup" << std::endl;
        // 可能抛出异常的操作
        bool should_throw = false; // 示例
        
        if (should_throw) {
            throw std::runtime_error("Derived cleanup failed");
        }
    }
};

class SafeDerived : public Base {
public:
    // 更安全的做法:覆盖release而不是cleanup
    void release() override {
        try {
            cleanup_safe();
            Base::release();
        } catch (const std::exception& e) {
            std::cerr << "Error in SafeDerived::release: " 
                      << e.what() << std::endl;
            throw; // 可以在release中抛出
        }
    }
    
private:
    void cleanup_safe() {
        std::cout << "SafeDerived cleanup" << std::endl;
        // 清理逻辑
    }
    
    // 覆盖cleanup但要保证不抛出
    void cleanup() noexcept override {
        try {
            cleanup_safe();
        } catch (...) {
            std::cerr << "Suppressed exception in SafeDerived::cleanup" 
                      << std::endl;
        }
    }
};

int main() {
    try {
        std::unique_ptr<Base> obj = std::make_unique<Derived>();
        
        // 最好显式释放
        obj->release();
        
    } catch (const std::exception& e) {
        std::cerr << "Caught: " << e.what() << std::endl;
    }
    
    // 即使不调用release,析构函数也是安全的
    {
        std::unique_ptr<Base> obj = std::make_unique<SafeDerived>();
        // 离开作用域时,析构函数不会抛出异常
    }
    
    return 0;
}

五、高级技巧和最佳实践

5.1 使用std::uncaught_exceptions()检测异常状态

// ✅ 正确示例:使用C++17的std::uncaught_exceptions()
#include <iostream>
#include <exception>

class SmartResource {
public:
    SmartResource() {
        std::cout << "SmartResource created" << std::endl;
    }
    
    ~SmartResource() {
        int uncaught = std::uncaught_exceptions();
        
        std::cout << "Destructor called with "
                  << uncaught << " uncaught exceptions" << std::endl;
        
        if (uncaught > 0) {
            // 有异常正在传播,进行最小化安全清理
            emergency_cleanup();
        } else {
            // 正常清理,可以进行复杂操作
            normal_cleanup();
        }
    }
    
    void use() {
        std::cout << "Using resource" << std::endl;
    }
    
private:
    void emergency_cleanup() noexcept {
        std::cout << "Performing emergency cleanup" << std::endl;
        // 只做不会抛出异常的操作
    }
    
    void normal_cleanup() {
        std::cout << "Performing normal cleanup" << std::endl;
        try {
            // 可能抛出异常的复杂清理
            if (should_fail()) {
                throw std::runtime_error("Normal cleanup failed");
            }
        } catch (const std::exception& e) {
            std::cerr << "Cleanup failed: " << e.what() << std::endl;
            // 记录但不重新抛出
        }
    }
    
    bool should_fail() { return false; } // 示例
};

void test_exception_state() {
    try {
        SmartResource res;
        res.use();
        throw std::runtime_error("Test exception");
    } catch (const std::exception& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }
}

int main() {
    test_exception_state();
    return 0;
}

5.2 双重保险模式

// ✅ 正确示例:双重保险模式
#include <iostream>
#include <memory>
#include <mutex>

class DoubleSafetyResource {
private:
    struct Impl;
    std::unique_ptr<Impl> impl_;
    
public:
    DoubleSafetyResource();
    ~DoubleSafetyResource() noexcept;
    
    // 显式关闭,可能抛出异常
    void close();
    
    // 使用资源
    void use();
    
private:
    void safe_cleanup() noexcept;
};

struct DoubleSafetyResource::Impl {
    FILE* file = nullptr;
    bool closed = false;
    std::mutex mutex;
    
    ~Impl() noexcept {
        safe_close();
    }
    
    void safe_close() noexcept {
        std::lock_guard<std::mutex> lock(mutex);
        if (file && !closed) {
            std::fclose(file);
            file = nullptr;
            closed = true;
        }
    }
    
    void close_with_checks() {
        std::lock_guard<std::mutex> lock(mutex);
        if (closed) {
            throw std::runtime_error("Already closed");
        }
        
        if (file) {
            // 复杂的关闭逻辑
            if (std::fclose(file) != 0) {
                throw std::runtime_error("Failed to close file");
            }
            file = nullptr;
            closed = true;
        }
    }
};

DoubleSafetyResource::DoubleSafetyResource() 
    : impl_(std::make_unique<Impl>()) {
    impl_->file = std::fopen("data.bin", "wb");
    if (!impl_->file) {
        throw std::runtime_error("Failed to open file");
    }
}

DoubleSafetyResource::~DoubleSafetyResource() noexcept {
    safe_cleanup();
}

void DoubleSafetyResource::safe_cleanup() noexcept {
    if (impl_) {
        impl_->safe_close();
    }
}

void DoubleSafetyResource::close() {
    if (impl_) {
        impl_->close_with_checks();
    }
}

void DoubleSafetyResource::use() {
    if (!impl_ || impl_->closed) {
        throw std::runtime_error("Resource not available");
    }
    // 使用资源
}

int main() {
    try {
        DoubleSafetyResource resource;
        
        resource.use();
        
        // 首选:显式关闭,可以处理异常
        resource.close();
        
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        // 析构函数仍会安全清理
    }
    
    return 0;
}

5.3 单元测试策略

// ✅ 正确示例:测试析构函数的异常安全性
#include <iostream>
#include <cassert>
#include <stdexcept>
#include <csignal>
#include <cstdlib>

// 测试辅助工具
class DestructorTestHarness {
    static bool terminate_called;
    
public:
    static void setup() {
        std::set_terminate([]() {
            terminate_called = true;
            std::cerr << "std::terminate called as expected" << std::endl;
            std::exit(0); // 正常退出测试
        });
    }
    
    static bool was_terminate_called() {
        return terminate_called;
    }
    
    static void reset() {
        terminate_called = false;
    }
};

bool DestructorTestHarness::terminate_called = false;

// 被测试的类
class TestClass {
public:
    enum class Mode {
        Safe,
        Throwing,
        DoubleThrow
    };
    
    static int instance_count;
    
    TestClass(Mode mode = Mode::Safe) : mode_(mode) {
        ++instance_count;
    }
    
    ~TestClass() noexcept(false) {
        --instance_count;
        
        switch (mode_) {
            case Mode::Safe:
                // 安全,不抛出
                break;
            case Mode::Throwing:
                throw std::runtime_error("TestClass destructor threw");
                break;
            case Mode::DoubleThrow:
                if (std::uncaught_exceptions() > 0) {
                    // 已经有异常在传播,再抛出一个会导致terminate
                    throw std::logic_error("Second exception during stack unwind");
                }
                break;
        }
    }
    
private:
    Mode mode_;
};

int TestClass::instance_count = 0;

// 测试用例
void test_safe_destructor() {
    std::cout << "Testing safe destructor..." << std::endl;
    DestructorTestHarness::reset();
    
    {
        TestClass obj(TestClass::Mode::Safe);
        assert(TestClass::instance_count == 1);
    }
    
    assert(TestClass::instance_count == 0);
    assert(!DestructorTestHarness::was_terminate_called());
    std::cout << "PASS" << std::endl;
}

void test_throwing_destructor_no_exception() {
    std::cout << "Testing throwing destructor (no active exception)..." << std::endl;
    DestructorTestHarness::reset();
    
    try {
        TestClass obj(TestClass::Mode::Throwing);
        // obj离开作用域,析构函数抛出异常
    } catch (const std::exception& e) {
        std::cout << "Caught: " << e.what() << std::endl;
        assert(TestClass::instance_count == 0);
        assert(!DestructorTestHarness::was_terminate_called());
        std::cout << "PASS" << std::endl;
        return;
    }
    
    assert(false && "Should have caught exception");
}

void test_double_throwing_destructor() {
    std::cout << "Testing double-throwing destructor..." << std::endl;
    DestructorTestHarness::reset();
    
    try {
        TestClass inner(TestClass::Mode::DoubleThrow);
        throw std::runtime_error("First exception");
    } catch (...) {
        // 内层对象在栈展开时析构,会抛出第二个异常
        // 这应该导致terminate
    }
    
    // 如果到达这里,测试失败
    assert(false && "Should have terminated");
}

int main() {
    DestructorTestHarness::setup();
    
    try {
        test_safe_destructor();
        test_throwing_destructor_no_exception();
        
        // 这个测试会导致terminate,所以单独运行
        // test_double_throwing_destructor();
        
        std::cout << "\nAll tests passed!" << std::endl;
        return 0;
        
    } catch (const std::exception& e) {
        std::cerr << "Test failed: " << e.what() << std::endl;
        return 1;
    }
}

六、总结和最佳实践

6.1 核心原则

  1. 绝不从析构函数中抛出异常
  2. 析构函数应声明为noexcept
  3. 使用RAII模式管理资源
  4. 提供显式释放函数处理可能失败的操作

6.2 设计模式

模式适用场景优点
显式释放函数需要处理可能失败的清理操作可以抛出异常,更好的错误处理
双重保险关键资源,必须保证清理即使显式释放失败,析构函数也能安全清理
资源收集器事务性操作,需要原子性支持提交/回滚语义
智能指针+自定义删除器通用资源管理自动生命周期管理

6.3 代码检查清单

// 析构函数设计检查清单
class YourClass {
public:
    // ✅ 正确做法
    ~YourClass() noexcept {           // 1. 添加noexcept
        try {
            cleanup();                // 2. 在try块中清理
        } catch (...) {
            // 3. 记录错误但不传播
            log_error("Destructor cleanup failed");
            // 4. 执行紧急清理(如果需要)
            emergency_cleanup();
        }
    }
    
    // ✅ 提供显式释放函数
    void release() {
        // 可以抛出异常
        complex_cleanup();
        released_ = true;
    }
    
private:
    void cleanup() noexcept {        // 5. 清理函数也声明noexcept
        if (!released_) {
            simple_cleanup();         // 6. 只做简单、安全的操作
        }
    }
    
    void complex_cleanup() {
        // 复杂逻辑,可能失败
    }
    
    void simple_cleanup() noexcept {
        // 简单逻辑,保证不失败
    }
    
    void emergency_cleanup() noexcept {
        // 最后的安全网
    }
    
    bool released_ = false;
};

6.4 现代C++特性

// C++17/20最佳实践
class ModernResource {
public:
    ModernResource() {
        // 获取资源
    }
    
    ~ModernResource() {
        // 使用if constexpr或concepts进行条件清理
        if constexpr (has_safe_cleanup_v<ResourceType>) {
            resource_.cleanup_safe();
        } else {
            // 后备方案
        }
    }
    
    // C++20概念约束的显式释放
    template<typename T>
    requires std::is_invocable_v<T, Resource&>
    void release_with(T&& cleanup_fn) {
        cleanup_fn(resource_);
        released_ = true;
    }
    
private:
    Resource resource_;
    bool released_ = false;
};

记住:析构函数中的异常就像是飞机降落时引擎失效——你无法安全地处理它,所以必须首先防止它发生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值