【穿越Effective C++】条款17:以独立语句将newed对象置入智能指针——异常安全的智能指针初始化
这个条款揭示了C++异常安全中一个微妙但危险的陷阱:在复合语句中创建智能指针可能导致资源泄漏。理解这一原则是构建异常安全代码的关键。
思维导图:智能指针初始化的异常安全完整体系

关键洞见与行动指南
必须遵守的核心原则:
- 独立语句初始化:总是在独立语句中创建智能指针,再传递给函数
- make_函数优先:优先使用
std::make_shared和std::make_unique - 工厂模式封装:使用工厂函数集中管理对象创建
- 资源立即接管:确保资源一旦分配就立即被RAII对象管理
现代C++开发建议:
- 代码规范要求:在团队规范中禁止在函数参数中直接new
- 静态分析集成:使用工具检测危险的智能指针初始化模式
- 审查清单:在代码审查中特别检查智能指针使用
- 异常安全测试:测试各种异常路径确保资源不泄漏
设计原则总结:
- RAII原则:资源获取后立即由对象接管
- 明确生命周期:清晰的资源所有权和生命周期管理
- 防御性编程:假设任何操作都可能抛出异常
- 零泄漏保证:确保在所有代码路径上资源正确释放
需要警惕的陷阱:
- 编译器优化差异:不同编译器可能产生不同的求值顺序
- 自定义删除器:删除器本身可能抛出异常
- 多参数函数:多个new操作增加异常安全复杂度
- 工厂方法设计:不正确的工厂实现可能引入泄漏
最终建议: 将独立语句初始化视为C++异常安全的"黄金法则"。培养"异常安全思维"——在编写每行代码时都问自己:“如果这里抛出异常,资源会被正确释放吗?” 这种前瞻性的思考方式是构建工业级C++系统的关键。
记住:在C++异常安全中,智能指针的正确初始化不是优化项,而是避免资源泄漏的基本要求。 条款17教会我们的不仅是一个编码技巧,更是对C++异常模型深刻理解的体现。
深入解析:异常安全的核心挑战
1. 问题根源:编译器求值顺序的不确定性
危险的复合语句初始化:
#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对象就会泄漏!}
}
编译器可能生成的代码分析:
// 编译器可能将上述代码转换为:
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. 实际验证资源泄漏
资源泄漏的实证演示:
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. 独立语句初始化
基本的安全初始化模式:
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 - 没有泄漏!
}
复杂场景的安全处理:
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的异常安全:
#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. 编译器求值顺序的复杂性
函数参数求值顺序的未定义行为:
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的性能和异常安全优势:
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. 自定义删除器的异常安全
自定义删除器的安全初始化:
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. 工厂模式的异常安全设计
异常安全的对象工厂:
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语句中直接newreturn 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. 通用安全初始化模板
适用于各种场景的安全初始化工具:
#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_ptrreturn 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. 代码审查和静态分析
识别危险模式的检查清单:
// 危险的代码模式 - 代码审查时应检查这些模式
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服务器连接管理
#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:图形渲染引擎资源管理
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;
}
