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

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