【穿越Effective C++】条款17:以独立语句将newed对象置入智能指针——异常安全的智能指针初始化

这个条款揭示了C++异常安全中一个微妙但危险的陷阱:在复合语句中创建智能指针可能导致资源泄漏。理解这一原则是构建异常安全代码的关键。


思维导图:智能指针初始化的异常安全完整体系


关键洞见与行动指南

必须遵守的核心原则:
  1. 独立语句初始化:总是在独立语句中创建智能指针,再传递给函数
  2. make_函数优先 :优先使用std::make_sharedstd::make_unique
  3. 工厂模式封装:使用工厂函数集中管理对象创建
  4. 资源立即接管:确保资源一旦分配就立即被RAII对象管理
现代C++开发建议:
  1. 代码规范要求:在团队规范中禁止在函数参数中直接new
  2. 静态分析集成:使用工具检测危险的智能指针初始化模式
  3. 审查清单:在代码审查中特别检查智能指针使用
  4. 异常安全测试:测试各种异常路径确保资源不泄漏
设计原则总结:
  1. RAII原则:资源获取后立即由对象接管
  2. 明确生命周期:清晰的资源所有权和生命周期管理
  3. 防御性编程:假设任何操作都可能抛出异常
  4. 零泄漏保证:确保在所有代码路径上资源正确释放
需要警惕的陷阱:
  1. 编译器优化差异:不同编译器可能产生不同的求值顺序
  2. 自定义删除器:删除器本身可能抛出异常
  3. 多参数函数:多个new操作增加异常安全复杂度
  4. 工厂方法设计:不正确的工厂实现可能引入泄漏

最终建议: 将独立语句初始化视为C++异常安全的"黄金法则"。培养"异常安全思维"------在编写每行代码时都问自己:"如果这里抛出异常,资源会被正确释放吗?" 这种前瞻性的思考方式是构建工业级C++系统的关键。

记住:在C++异常安全中,智能指针的正确初始化不是优化项,而是避免资源泄漏的基本要求。 条款17教会我们的不仅是一个编码技巧,更是对C++异常模型深刻理解的体现。


深入解析:异常安全的核心挑战

1. 问题根源:编译器求值顺序的不确定性

危险的复合语句初始化:

cpp 复制代码
#include <memory>
#include <iostream>

class Investment {
public:
    Investment() { 
        std::cout << "Investment构造" << std::endl; 
    }
    virtual ~Investment() { 
        std::cout << "Investment析构" << std::endl; 
    }
    virtual void calculate() = 0;
};

class Stock : public Investment {
public:
    Stock() { 
        std::cout << "Stock构造" << std::endl; 
    }
    ~Stock() override { 
        std::cout << "Stock析构" << std::endl; 
    }
    void calculate() override {
        std::cout << "计算股票收益" << std::endl;
    }
};

// 一个可能抛出异常的函数
void riskyOperation(const std::string& message) {
    std::cout << "执行风险操作: " << message << std::endl;
    // 模拟可能抛出异常的操作
    if (message.empty()) {
        throw std::invalid_argument("消息不能为空");
    }
}

// 处理投资的函数
void processInvestment(std::shared_ptr<Investment> inv, 
                      const std::string& logMessage) {
    std::cout << "处理投资: " << logMessage << std::endl;
    inv->calculate();
}

void demonstrate_dangerous_initialization() {
    std::cout << "=== 危险的智能指针初始化 ===" << std::endl;
    
    try {
        // 危险:在函数调用中直接创建智能指针!
        processInvestment(
            std::shared_ptr<Investment>(new Stock),  // 可能泄漏!
            riskyOperation("开始处理")               // 可能抛出异常!
        );
        
        // 问题:编译器可能生成这样的执行顺序:
        // 1. new Stock - 分配内存并构造对象
        // 2. riskyOperation("开始处理") - 可能抛出异常!
        // 3. std::shared_ptr构造函数 - 如果2抛出异常,这一步不会执行
        //
        // 结果:Stock对象已构造但未被智能指针管理,内存泄漏!
        
    } catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
        // 如果异常在new之后、shared_ptr构造之前发生,
        // Stock对象就会泄漏!
    }
}

编译器可能生成的代码分析:

cpp 复制代码
// 编译器可能将上述代码转换为:
void compiler_generated_dangerous_code() {
    // 可能的执行顺序1(安全):
    // 1. std::shared_ptr<Investment> temp(new Stock)
    // 2. riskyOperation("开始处理")
    // 3. processInvestment(temp, ...)
    
    // 可能的执行顺序2(危险):
    // 1. Investment* rawPtr = new Stock
    // 2. riskyOperation("开始处理")  // 如果这里抛出异常,rawPtr泄漏!
    // 3. std::shared_ptr<Investment> temp(rawPtr)
    // 4. processInvestment(temp, ...)
    
    // 可能的执行顺序3(同样危险):
    // 1. riskyOperation("开始处理")  // 先执行参数求值
    // 2. Investment* rawPtr = new Stock
    // 3. std::shared_ptr<Investment> temp(rawPtr)
    // 4. processInvestment(temp, ...)
    
    // C++标准没有规定函数参数的求值顺序!
    // 不同编译器、不同优化级别可能产生不同顺序!
}
2. 实际验证资源泄漏

资源泄漏的实证演示:

cpp 复制代码
class LeakDetector {
public:
    static int constructionCount;
    static int destructionCount;
    
    LeakDetector() {
        ++constructionCount;
        std::cout << "LeakDetector构造 #" << constructionCount << std::endl;
    }
    
    ~LeakDetector() {
        ++destructionCount;
        std::cout << "LeakDetector析构 #" << destructionCount << std::endl;
    }
    
    static void report() {
        std::cout << "构造/析构统计: " << constructionCount 
                  << " / " << destructionCount << std::endl;
        if (constructionCount > destructionCount) {
            std::cout << "*** 检测到资源泄漏! ***" << std::endl;
        }
    }
};

int LeakDetector::constructionCount = 0;
int LeakDetector::destructionCount = 0;

void dangerousProcess(std::shared_ptr<LeakDetector> ptr, bool shouldThrow) {
    if (shouldThrow) {
        throw std::runtime_error("处理过程中发生异常");
    }
    std::cout << "安全处理对象" << std::endl;
}

void demonstrate_resource_leak() {
    LeakDetector::constructionCount = 0;
    LeakDetector::destructionCount = 0;
    
    std::cout << "=== 演示资源泄漏 ===" << std::endl;
    
    try {
        // 危险的复合语句初始化
        dangerousProcess(
            std::shared_ptr<LeakDetector>(new LeakDetector),
            true  // 强制抛出异常
        );
        
    } catch (const std::exception& e) {
        std::cout << "异常: " << e.what() << std::endl;
    }
    
    LeakDetector::report();
    
    // 在某些编译器/优化级别下,可能会看到:
    // 构造/析构统计: 1 / 0
    // *** 检测到资源泄漏! ***
}

解决方案:安全的初始化模式

1. 独立语句初始化

基本的安全初始化模式:

cpp 复制代码
void demonstrate_safe_initialization() {
    std::cout << "\n=== 安全的智能指针初始化 ===" << std::endl;
    
    LeakDetector::constructionCount = 0;
    LeakDetector::destructionCount = 0;
    
    try {
        // 安全:在独立语句中创建智能指针
        std::shared_ptr<LeakDetector> safePtr(new LeakDetector);
        
        // 现在safePtr已经接管资源,即使后面抛出异常也会正确释放
        dangerousProcess(safePtr, true);  // 强制抛出异常
        
    } catch (const std::exception& e) {
        std::cout << "异常: " << e.what() << std::endl;
    }
    
    LeakDetector::report();
    // 现在会看到:构造/析构统计: 1 / 1 - 没有泄漏!
}

复杂场景的安全处理:

cpp 复制代码
class DatabaseConnection {
public:
    DatabaseConnection(const std::string& connectionString) {
        std::cout << "建立数据库连接: " << connectionString << std::endl;
    }
    
    ~DatabaseConnection() {
        std::cout << "关闭数据库连接" << std::endl;
    }
    
    void execute(const std::string& query) {
        std::cout << "执行查询: " << query << std::endl;
    }
};

class Transaction {
public:
    Transaction(const std::string& name) {
        std::cout << "开始事务: " << name << std::endl;
    }
    
    ~Transaction() {
        std::cout << "结束事务" << std::endl;
    }
    
    void commit() {
        std::cout << "提交事务" << std::endl;
    }
};

void processBusinessLogic(std::shared_ptr<DatabaseConnection> db,
                         std::shared_ptr<Transaction> tx,
                         const std::string& operation) {
    std::cout << "处理业务逻辑: " << operation << std::endl;
    db->execute("SELECT * FROM data");
    tx->commit();
}

void demonstrate_complex_safe_initialization() {
    std::cout << "\n=== 复杂场景的安全初始化 ===" << std::endl;
    
    try {
        // 危险做法:在函数调用中直接创建多个智能指针
        // processBusinessLogic(
        //     std::shared_ptr<DatabaseConnection>(new DatabaseConnection("Server=DB1")),
        //     std::shared_ptr<Transaction>(new Transaction("MainTx")),
        //     "重要操作"
        // );
        // 多个new操作,异常安全窗口更大!
        
        // 安全做法:独立语句初始化所有智能指针
        auto dbConn = std::shared_ptr<DatabaseConnection>(
            new DatabaseConnection("Server=DB1"));
        
        auto transaction = std::shared_ptr<Transaction>(
            new Transaction("MainTx"));
        
        // 现在所有资源都被智能指针管理,异常安全
        processBusinessLogic(dbConn, transaction, "重要操作");
        
    } catch (const std::exception& e) {
        std::cout << "业务逻辑异常: " << e.what() << std::endl;
        // 即使发生异常,dbConn和transaction也会正确析构
    }
}
2. 现代C++的make_函数

make_shared和make_unique的异常安全:

cpp 复制代码
#include <memory>

void demonstrate_make_functions() {
    std::cout << "\n=== 使用make_shared和make_unique ===" << std::endl;
    
    // make_shared是异常安全的 - 这是首选方案!
    auto safePtr1 = std::make_shared<LeakDetector>();
    
    // make_unique (C++14) 同样是异常安全的
    auto safePtr2 = std::make_unique<LeakDetector>();
    
    // make_函数在单次操作中完成内存分配和对象构造
    // 不存在"new成功但智能指针未接管"的窗口期
    
    std::cout << "使用make_函数创建智能指针 - 天生异常安全" << std::endl;
}

// 带有参数的make_函数使用
class ConfigurableResource {
private:
    std::string name;
    int configuration;
    
public:
    ConfigurableResource(const std::string& n, int config) 
        : name(n), configuration(config) {
        std::cout << "创建可配置资源: " << name 
                  << " [配置=" << configuration << "]" << std::endl;
    }
    
    ~ConfigurableResource() {
        std::cout << "销毁可配置资源: " << name << std::endl;
    }
    
    void use() {
        std::cout << "使用资源: " << name << std::endl;
    }
};

void demonstrate_make_with_parameters() {
    std::cout << "\n=== 带参数的make_函数 ===" << std::endl;
    
    // make_shared带参数 - 异常安全
    auto resource = std::make_shared<ConfigurableResource>("数据库", 42);
    resource->use();
    
    // 对比危险做法:
    // auto dangerous = std::shared_ptr<ConfigurableResource>(
    //     new ConfigurableResource("临时资源", riskyOperation()));
    // 如果riskyOperation()抛出异常,new的对象就会泄漏
    
    // 安全做法:要么用make_shared,要么独立语句
    int config = 100;  // 假设这是复杂计算的结果
    auto safe = std::make_shared<ConfigurableResource>("安全资源", config);
}

技术原理深度解析

1. 编译器求值顺序的复杂性

函数参数求值顺序的未定义行为:

cpp 复制代码
void analyze_evaluation_order() {
    std::cout << "\n=== 函数参数求值顺序分析 ===" << std::endl;
    
    auto logger = [](const std::string& name, int order) {
        std::cout << "参数 '" << name << "' 第 " << order << " 个求值" << std::endl;
        return order;
    };
    
    // 测试函数参数求值顺序
    auto testFunc = [](int first, int second, int third) {
        std::cout << "函数调用执行" << std::endl;
    };
    
    // C++标准没有规定参数求值顺序!
    // 不同编译器可能产生不同结果
    testFunc(
        logger("第一个参数", 1),
        logger("第二个参数", 2), 
        logger("第三个参数", 3)
    );
    
    // 可能的输出1(从左到右):
    // 参数 '第一个参数' 第 1 个求值
    // 参数 '第二个参数' 第 2 个求值  
    // 参数 '第三个参数' 第 3 个求值
    // 函数调用执行
    
    // 可能的输出2(从右到左):
    // 参数 '第三个参数' 第 3 个求值
    // 参数 '第二个参数' 第 2 个求值
    // 参数 '第一个参数' 第 1 个求值
    // 函数调用执行
    
    // 可能的输出3(任意顺序):
    // 参数 '第二个参数' 第 2 个求值
    // 参数 '第一个参数' 第 1 个求值
    // 参数 '第三个参数' 第 3 个求值
    // 函数调用执行
}
2. make_shared的优化优势

make_shared的性能和异常安全优势:

cpp 复制代码
void demonstrate_make_shared_advantages() {
    std::cout << "\n=== make_shared的额外优势 ===" << std::endl;
    
    // 传统方式:两次内存分配
    // 1. 分配对象内存
    // 2. 分配控制块内存(引用计数等)
    std::shared_ptr<LeakDetector> traditional(new LeakDetector);
    
    // make_shared方式:一次内存分配
    // 对象和控制块在连续内存中分配
    auto optimized = std::make_shared<LeakDetector>();
    
    std::cout << "make_shared优势:" << std::endl;
    std::cout << "1. 异常安全 - 没有资源泄漏窗口" << std::endl;
    std::cout << "2. 性能优化 - 单次内存分配" << std::endl; 
    std::cout << "3. 缓存友好 - 对象和控制块位置接近" << std::endl;
    std::cout << "4. 代码简洁 - 不需要重复类型名" << std::endl;
}

特殊场景处理

1. 自定义删除器的异常安全

自定义删除器的安全初始化:

cpp 复制代码
void demonstrate_custom_deleter_safety() {
    std::cout << "\n=== 自定义删除器的异常安全 ===" << std::endl;
    
    // 自定义删除器 - 可能抛出异常!
    auto throwingDeleter = [](LeakDetector* ptr) {
        std::cout << "自定义删除器执行" << std::endl;
        if (ptr) {
            // 模拟可能抛出异常的操作
            throw std::runtime_error("删除器操作失败");
            delete ptr;
        }
    };
    
    try {
        // 危险:在函数参数中创建带自定义删除器的智能指针
        // processResource(
        //     std::shared_ptr<LeakDetector>(new LeakDetector, throwingDeleter),
        //     "测试操作"
        // );
        
        // 安全:独立语句初始化
        auto resource = std::shared_ptr<LeakDetector>(
            new LeakDetector, throwingDeleter);
        
        // 现在资源已被管理,即使后面抛出异常...
        throw std::logic_error("业务逻辑异常");
        
    } catch (const std::exception& e) {
        std::cout << "捕获异常: " << e.what() << std::endl;
        // 即使删除器本身可能抛出异常,但资源已经被智能指针管理
        // 会在栈展开时尝试调用删除器
    }
}
2. 工厂模式的异常安全设计

异常安全的对象工厂:

cpp 复制代码
class ObjectFactory {
public:
    // 异常安全的工厂方法
    static std::shared_ptr<ConfigurableResource> createSafeResource(
        const std::string& name, int config) {
        
        // 使用make_shared确保异常安全
        return std::make_shared<ConfigurableResource>(name, config);
    }
    
    // 危险的传统工厂方法
    static std::shared_ptr<ConfigurableResource> createDangerousResource(
        const std::string& name, int config) {
        
        // 危险:在return语句中直接new
        return std::shared_ptr<ConfigurableResource>(
            new ConfigurableResource(name, config));
        // 虽然这里看起来安全,但如果有复杂表达式仍可能有问题
    }
    
    // 带自定义删除器的安全工厂
    static std::shared_ptr<DatabaseConnection> createDatabaseConnection(
        const std::string& connectionString) {
        
        // 安全:独立语句创建
        auto deleter = [](DatabaseConnection* db) {
            std::cout << "自定义数据库连接清理" << std::endl;
            delete db;
        };
        
        auto connection = std::shared_ptr<DatabaseConnection>(
            new DatabaseConnection(connectionString), deleter);
            
        return connection;
    }
};

void demonstrate_factory_pattern() {
    std::cout << "\n=== 异常安全的工厂模式 ===" << std::endl;
    
    try {
        // 安全使用工厂方法
        auto resource = ObjectFactory::createSafeResource("工厂资源", 123);
        auto dbConnection = ObjectFactory::createDatabaseConnection("Server=Factory");
        
        // 即使后续操作抛出异常,资源也是安全的
        throw std::runtime_error("测试异常");
        
    } catch (const std::exception& e) {
        std::cout << "工厂使用异常: " << e.what() << std::endl;
    }
}

现代C++的最佳实践

1. 通用安全初始化模板

适用于各种场景的安全初始化工具:

cpp 复制代码
#include <type_traits>
#include <utility>

namespace safe_initialization {
    
    // 安全初始化工具函数
    template<typename T, typename... Args>
    std::shared_ptr<T> make_shared_safe(Args&&... args) {
        // 只是make_shared的别名,强调异常安全性
        return std::make_shared<T>(std::forward<Args>(args)...);
    }
    
    template<typename T, typename Deleter, typename... Args>
    std::shared_ptr<T> make_shared_with_deleter(Deleter&& deleter, Args&&... args) {
        // 安全初始化带删除器的shared_ptr
        return std::shared_ptr<T>(
            new T(std::forward<Args>(args)...),
            std::forward<Deleter>(deleter));
    }
    
    // 概念检查(C++20)
    template<typename T>
    concept ExceptionSafeInitializable = 
        std::is_nothrow_constructible_v<T> || 
        requires { typename std::enable_if_t<std::is_class_v<T>>; };
    
    template<ExceptionSafeInitializable T, typename... Args>
    std::unique_ptr<T> make_unique_safe(Args&&... args) {
        return std::make_unique<T>(std::forward<Args>(args)...);
    }
}

void demonstrate_safe_utilities() {
    std::cout << "\n=== 安全初始化工具 ===" << std::endl;
    
    using namespace safe_initialization;
    
    // 使用安全工具函数
    auto safeResource = make_shared_safe<ConfigurableResource>("工具资源", 999);
    
    auto customDeleter = [](auto* ptr) { 
        std::cout << "自定义删除" << std::endl;
        delete ptr; 
    };
    
    auto safeWithDeleter = make_shared_with_deleter<LeakDetector>(customDeleter);
    
    std::cout << "安全初始化工具确保异常安全" << std::endl;
}
2. 代码审查和静态分析

识别危险模式的检查清单:

cpp 复制代码
// 危险的代码模式 - 代码审查时应检查这些模式
class CodeReviewExamples {
public:
    void dangerousPatterns() {
        // 模式1:函数参数中直接new
        // processResource(std::shared_ptr<Resource>(new Resource), arg);
        
        // 模式2:多个智能指针在参数中创建
        // processMultiple(
        //     std::shared_ptr<A>(new A),
        //     std::shared_ptr<B>(new B)  // 多个new,风险更大!
        // );
        
        // 模式3:带复杂表达式的new
        // processComplex(
        //     std::shared_ptr<Resource>(new Resource(complexFunction())),
        //     otherArg
        // );
        
        // 模式4:return语句中直接new
        // return std::shared_ptr<Resource>(new Resource(args));
    }
    
    void safePatterns() {
        // 安全模式1:独立语句初始化
        auto resource = std::make_shared<LeakDetector>();
        processResource(resource, arg);
        
        // 安全模式2:使用make_函数
        return std::make_shared<LeakDetector>();
        
        // 安全模式3:工厂方法
        return ObjectFactory::createSafeResource("safe", 123);
    }
};

实战案例:复杂系统设计

案例1:Web服务器连接管理
cpp 复制代码
#include <memory>
#include <vector>

class HttpRequest {
public:
    HttpRequest(const std::string& method, const std::string& path) 
        : method_(method), path_(path) {
        std::cout << "创建HTTP请求: " << method << " " << path << std::endl;
    }
    
    ~HttpRequest() {
        std::cout << "销毁HTTP请求: " << method_ << " " << path_ << std::endl;
    }
    
    void process() {
        std::cout << "处理请求: " << method_ << " " << path_ << std::endl;
    }
    
private:
    std::string method_;
    std::string path_;
};

class DatabaseConnection {
public:
    DatabaseConnection(const std::string& dbName) : dbName_(dbName) {
        std::cout << "建立数据库连接: " << dbName << std::endl;
    }
    
    ~DatabaseConnection() {
        std::cout << "关闭数据库连接: " << dbName_ << std::endl;
    }
    
    void query(const std::string& sql) {
        std::cout << "数据库查询: " << sql << " on " << dbName_ << std::endl;
    }
    
private:
    std::string dbName_;
};

class RequestProcessor {
private:
    std::shared_ptr<DatabaseConnection> dbConn_;
    
public:
    RequestProcessor(const std::string& dbName) {
        // 安全:独立语句初始化数据库连接
        dbConn_ = std::make_shared<DatabaseConnection>(dbName);
    }
    
    void processRequest(const std::string& method, const std::string& path) {
        // 安全:独立语句创建请求对象
        auto request = std::make_shared<HttpRequest>(method, path);
        
        // 安全:所有资源都已由智能指针管理
        try {
            request->process();
            dbConn_->query("SELECT * FROM users WHERE active = 1");
            
            // 模拟可能抛出异常的业务逻辑
            if (path == "/dangerous") {
                throw std::runtime_error("危险路径处理失败");
            }
            
        } catch (const std::exception& e) {
            std::cout << "请求处理异常: " << e.what() << std::endl;
            // 即使发生异常,request和dbConn_也会正确释放
        }
    }
};

void demonstrate_web_server_safety() {
    std::cout << "\n=== Web服务器异常安全演示 ===" << std::endl;
    
    RequestProcessor processor("UserDatabase");
    
    try {
        // 正常请求
        processor.processRequest("GET", "/api/users");
        
        // 危险请求 - 会抛出异常
        processor.processRequest("POST", "/dangerous");
        
    } catch (const std::exception& e) {
        std::cout << "服务器异常: " << e.what() << std::endl;
    }
    
    std::cout << "Web服务器演示结束" << std::endl;
}
案例2:图形渲染引擎资源管理
cpp 复制代码
class Texture {
public:
    Texture(const std::string& name, int width, int height) 
        : name_(name), width_(width), height_(height) {
        std::cout << "加载纹理: " << name << " (" << width << "x" << height << ")" << std::endl;
    }
    
    ~Texture() {
        std::cout << "卸载纹理: " << name_ << std::endl;
    }
    
    void bind() {
        std::cout << "绑定纹理: " << name_ << std::endl;
    }
    
private:
    std::string name_;
    int width_, height_;
};

class Shader {
public:
    Shader(const std::string& name, const std::string& source) 
        : name_(name) {
        std::cout << "编译着色器: " << name << std::endl;
    }
    
    ~Shader() {
        std::cout << "销毁着色器: " << name_ << std::endl;
    }
    
    void use() {
        std::cout << "使用着色器: " << name_ << std::endl;
    }
    
private:
    std::string name_;
};

class Renderer {
private:
    std::shared_ptr<Texture> diffuseMap_;
    std::shared_ptr<Texture> normalMap_;
    std::shared_ptr<Shader> mainShader_;
    
public:
    Renderer() {
        // 安全初始化所有图形资源
        initializeResources();
    }
    
    void initializeResources() {
        std::cout << "=== 初始化图形资源 ===" << std::endl;
        
        // 安全:独立语句初始化每个资源
        diffuseMap_ = std::make_shared<Texture>("diffuse", 1024, 1024);
        normalMap_ = std::make_shared<Texture>("normal", 512, 512);
        mainShader_ = std::make_shared<Shader>("main", "vertex...fragment...");
        
        std::cout << "所有图形资源初始化完成" << std::endl;
    }
    
    void renderFrame() {
        try {
            // 安全使用已初始化的资源
            mainShader_->use();
            diffuseMap_->bind();
            normalMap_->bind();
            
            std::cout << "渲染帧完成" << std::endl;
            
            // 模拟渲染错误
            throw std::runtime_error("GPU设备丢失");
            
        } catch (const std::exception& e) {
            std::cout << "渲染错误: " << e.what() << std::endl;
            // 即使发生异常,所有纹理和着色器也会正确释放
        }
    }
};

void demonstrate_graphics_engine() {
    std::cout << "\n=== 图形引擎异常安全演示 ===" << std::endl;
    
    {
        Renderer renderer;
        renderer.renderFrame();
    } // Renderer析构时自动释放所有资源
    
    std::cout << "图形引擎演示结束" << std::endl;
}
相关推荐
moiumxf0278q2 小时前
C++中智能指针是如何工作的?
java·jvm·c++
似水এ᭄往昔3 小时前
【C++】--模板进阶
开发语言·c++
AA陈超3 小时前
虚幻引擎5 GAS开发俯视角RPG游戏 P07-11 实现自动运行
c++·游戏·ue5·游戏引擎·虚幻
DARLING Zero two♡3 小时前
【优选算法】LinkedList-Concatenate:链表的算法之契
数据结构·c++·算法·链表
yolo_guo3 小时前
opencv 学习: 07 使用迭代器 (iterator) 遍历像素
linux·c++·opencv
mjhcsp4 小时前
C++ 高精度计算:突破数据类型限制的实现与应用
开发语言·c++·算法·高精度
lixinnnn.4 小时前
C++: map和set
开发语言·c++
大袁同学4 小时前
【二叉搜索树】:程序的“决策树”,排序数据的基石
数据结构·c++·算法·决策树·stl
郝学胜-神的一滴4 小时前
Qt QPushButton 样式完全指南:从基础到高级实现
linux·开发语言·c++·qt·程序人生