C++ 智能指针完全指南: 智能指针概述,原始指针的问题,unique_ptr详解,shared_ptr详解,weak_ptr详解,智能指针最佳实践 实战示例
·
智能指针概述
什么是智能指针
智能指针是C++ RAII(Resource Acquisition Is Initialization)思想的典型应用,它们在构造时获取资源,在析构时自动释放资源,从而避免内存泄漏。
主要类型
-
unique_ptr:独占所有权的智能指针 -
shared_ptr:共享所有权的智能指针 -
weak_ptr:不共享所有权的观察指针
原始指针的问题
常见内存问题
cpp
// 问题1: 内存泄漏
void memory_leak() {
int* ptr = new int(42);
// 忘记 delete ptr;
}
// 问题2: 重复释放
void double_delete() {
int* ptr = new int(42);
delete ptr;
delete ptr; // 未定义行为
}
// 问题3: 野指针
void dangling_pointer() {
int* ptr = new int(42);
delete ptr;
*ptr = 100; // 未定义行为
}
unique_ptr详解
基本用法
cpp
#include <memory>
#include <iostream>
void unique_ptr_basic() {
// 创建 unique_ptr
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
// 访问数据
std::cout << *ptr1 << std::endl; // 42
arr[0] = 1;
// 释放所有权
int* raw_ptr = ptr1.release();
delete raw_ptr;
// 重置指针
ptr1.reset(new int(100));
// 自动释放内存
} // ptr1 和 arr 自动释放
独占所有权特性
cpp
void ownership_demo() {
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
// 错误: 不能复制
// std::unique_ptr<int> ptr2 = ptr1;
// 正确: 转移所有权
std::unique_ptr<int> ptr2 = std::move(ptr1);
std::cout << "ptr1: " << (ptr1 ? "not null" : "null") << std::endl;
std::cout << "ptr2: " << *ptr2 << std::endl;
}
自定义删除器
cpp
struct FileDeleter {
void operator()(FILE* file) {
if (file) {
fclose(file);
std::cout << "File closed" << std::endl;
}
}
};
void custom_deleter_demo() {
std::unique_ptr<FILE, FileDeleter> filePtr(fopen("test.txt", "w"));
if (filePtr) {
fputs("Hello World", filePtr.get());
}
// 文件自动关闭
}
shared_ptr详解
基本用法
cpp
void shared_ptr_basic() {
// 创建 shared_ptr
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1; // 共享所有权
std::cout << "Value: " << *ptr1 << std::endl;
std::cout << "Use count: " << ptr1.use_count() << std::endl; // 2
ptr1.reset(); // 释放一个引用
std::cout << "Use count after reset: " << ptr2.use_count() << std::endl; // 1
}
引用计数机制
cpp
class Resource {
public:
Resource() { std::cout << "Resource created" << std::endl; }
~Resource() { std::cout << "Resource destroyed" << std::endl; }
void doSomething() { std::cout << "Doing something" << std::endl; }
};
void reference_counting_demo() {
std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>();
{
std::shared_ptr<Resource> ptr2 = ptr1;
std::cout << "Use count inside block: " << ptr1.use_count() << std::endl; // 2
}
std::cout << "Use count outside block: " << ptr1.use_count() << std::endl; // 1
// Resource 在 ptr1 析构时销毁
}
循环引用问题
cpp
struct Node {
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev;
std::string name;
Node(const std::string& n) : name(n) {
std::cout << "Node " << name << " created" << std::endl;
}
~Node() {
std::cout << "Node " << name << " destroyed" << std::endl;
}
};
void circular_reference_demo() {
auto node1 = std::make_shared<Node>("A");
auto node2 = std::make_shared<Node>("B");
// 创建循环引用
node1->next = node2;
node2->prev = node1;
// node1 和 node2 都不会被销毁!
// 因为引用计数永远不会降到0
}
weak_ptr详解
解决循环引用
cpp
struct SafeNode {
std::shared_ptr<SafeNode> next;
std::weak_ptr<SafeNode> prev; // 使用 weak_ptr 打破循环引用
std::string name;
SafeNode(const std::string& n) : name(n) {
std::cout << "SafeNode " << name << " created" << std::endl;
}
~SafeNode() {
std::cout << "SafeNode " << name << " destroyed" << std::endl;
}
};
void weak_ptr_solution() {
auto node1 = std::make_shared<SafeNode>("A");
auto node2 = std::make_shared<SafeNode>("B");
node1->next = node2;
node2->prev = node1; // weak_ptr 不会增加引用计数
// 现在 node1 和 node2 可以被正确销毁
}
weak_ptr 使用方法
cpp
void weak_ptr_usage() {
std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared;
// 检查对象是否还存在
if (auto locked = weak.lock()) {
std::cout << "Value: " << *locked << std::endl;
std::cout << "Use count: " << shared.use_count() << std::endl; // 2
}
shared.reset();
if (weak.expired()) {
std::cout << "Object has been destroyed" << std::endl;
}
}
智能指针最佳实践
1. 优先使用 make_shared 和 make_unique
cpp
// 推荐
auto ptr1 = std::make_unique<int>(42);
auto ptr2 = std::make_shared<std::string>("Hello");
// 不推荐
std::unique_ptr<int> ptr3(new int(42));
std::shared_ptr<std::string> ptr4(new std::string("Hello"));
2. 明确所有权语义
cpp
// 工厂函数返回 unique_ptr
std::unique_ptr<Resource> createResource() {
return std::make_unique<Resource>();
}
// 需要共享所有权时转换为 shared_ptr
void useSharedResource() {
auto resource = createResource();
std::shared_ptr<Resource> sharedResource = std::move(resource);
}
3. 避免原始指针和智能指针混用
cpp
void avoid_mixing() {
// 危险做法
int* rawPtr = new int(42);
std::shared_ptr<int> smartPtr(rawPtr);
// 安全做法
auto smartPtr = std::make_shared<int>(42);
}
4. 使用 weak_ptr 观察 shared_ptr
cpp
class Observer {
std::weak_ptr<Resource> resource_;
public:
void setResource(std::shared_ptr<Resource> resource) {
resource_ = resource;
}
void useResource() {
if (auto resource = resource_.lock()) {
resource->doSomething();
} else {
std::cout << "Resource no longer available" << std::endl;
}
}
};
实战示例
示例1:资源管理类
cpp
class DatabaseConnection {
private:
std::unique_ptr<sqlite3, decltype(&sqlite3_close)> db_;
public:
DatabaseConnection(const std::string& filename)
: db_(nullptr, sqlite3_close) {
sqlite3* db;
if (sqlite3_open(filename.c_str(), &db) == SQLITE_OK) {
db_.reset(db);
}
}
bool isValid() const { return db_ != nullptr; }
sqlite3* get() const { return db_.get(); }
// 自动关闭数据库连接
};
示例2:对象池模式
cpp
template<typename T>
class ObjectPool {
private:
std::vector<std::shared_ptr<T>> pool_;
std::weak_ptr<T> createObject() {
auto obj = std::make_shared<T>();
pool_.push_back(obj);
return obj;
}
public:
std::shared_ptr<T> acquire() {
// 清理已销毁的对象
pool_.erase(
std::remove_if(pool_.begin(), pool_.end(),
[](const std::shared_ptr<T>& ptr) { return ptr.use_count() == 1; }),
pool_.end()
);
return createObject().lock();
}
size_t size() const { return pool_.size(); }
};
示例3:缓存系统
cpp
template<typename Key, typename Value>
class Cache {
private:
std::unordered_map<Key, std::weak_ptr<Value>> cache_;
mutable std::mutex mutex_;
public:
std::shared_ptr<Value> get(const Key& key) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(key);
if (it != cache_.end()) {
if (auto value = it->second.lock()) {
return value; // 缓存命中
} else {
cache_.erase(it); // 清理过期条目
}
}
return nullptr; // 缓存未命中
}
void set(const Key& key, std::shared_ptr<Value> value) {
std::lock_guard<std::mutex> lock(mutex_);
cache_[key] = value;
}
};
示例4:多线程安全使用
cpp
class ThreadSafeCounter {
private:
std::shared_ptr<int> counter_ = std::make_shared<int>(0);
std::mutex mutex_;
public:
void increment() {
std::lock_guard<std::mutex> lock(mutex_);
++(*counter_);
}
std::shared_ptr<int> getCounter() {
std::lock_guard<std::mutex> lock(mutex_);
return counter_;
}
int getValue() {
std::lock_guard<std::mutex> lock(mutex_);
return *counter_;
}
};
总结
通过合理使用智能指针,你可以:
-
彻底告别内存泄漏 - 资源自动管理
-
提高代码安全性 - 避免悬空指针和重复释放
-
明确所有权语义 - 让代码意图更清晰
-
简化异常安全 - 即使在异常情况下资源也能正确释放
-
提高开发效率 - 减少手动内存管理的工作量
记住黄金法则:能用智能指针就绝不用原始指针,让C++的内存管理变得简单而安全!
更多推荐



所有评论(0)