好的,这是关于智能指针几个核心高级问题的详细解答。
1. 如何选择 unique_ptr 和 shared_ptr?
这是一个关于所有权模型的问题。所有权的概念是智能指针的核心。
| 特性 | std::unique_ptr |
std::shared_ptr |
|---|---|---|
| 所有权模型 | 独占所有权 | 共享所有权 |
| 拷贝语义 | ❌ 不可拷贝,只可移动 | ✅ 可以拷贝 |
| 性能开销 | 几乎为零(与裸指针无异) | 有开销(控制块、引用计数原子操作) |
| 适用场景 | 明确知道只有一个所有者;父对象拥有子对象;工厂模式返回指针 | 需要多个独立对象共同管理同一个资源的生命周期;缓存中需要共享数据 |
选择指南:
默认使用
unique_ptr这是 C++11/14/17 以来的最佳实践。它清晰地表达了“我是这个资源的唯一主人”的语义,没有额外开销,并且能防止意外的资源分享。绝大多数场景下,一个资源在任一时刻都只有一个明确的拥有者。// 场景1: 在函数内部动态创建对象,且不想手动管理内存 void process() { auto data = std::make_unique<MyData>(); data->doSomething(); // data 离开作用域,自动删除 MyData } // 场景2: 作为类的成员变量,表示“组合”关系(我拥有它,我的生命周期就是它的生命周期) class Game { private: std::unique_ptr<Renderer> m_renderer; // Game *拥有* Renderer public: Game() : m_renderer(std::make_unique<Renderer>()) {} }; // 场景3: 工厂函数返回资源 std::unique_ptr<Connection> createConnection() { return std::make_unique<Connection>(); }当且仅当需要共享所有权时,才使用
shared_ptr当你无法确定哪个对象会最后使用这个资源时,就需要共享所有权。所有持有该shared_ptr的对象共同决定资源的生命周期。// 场景1: 多个对象需要共享同一份数据 class Node { public: std::vector<std::shared_ptr<Node>> children; // 父子节点共享 ownership? // ... (但注意这可能导致循环引用!) }; // 场景2: 缓存系统 std::map<std::string, std::shared_ptr<Config>> cache; auto config = cache["app"]; // 多个部分都可能持有这个缓存的配置 // 场景3: 异步/多线程任务中,任务和主线程都不知道对方会先结束 void asyncTask(std::shared_ptr<NetworkSession> session) { // asyncTask 和创建它的函数都可能比对方存活得更久 // shared_ptr保证了只要有一方在用,session就有效。 }
2. How to Solve the Circular Reference Problem of shared_ptr?
循环引用是 shared_ptr 最经典的问题。
What is a Circular Reference?
#include <memory>
class B; // Forward declaration
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B destroyed\n"; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b\_ptr = b; // A ref-count of B: 2
b->a\_ptr = a; // B ref-count of A: \033[0;31m2\033[0m
return O;
// a ref-count -> \033[0;31m1\033[0m (because b->a\_ptr still holds)
// b ref-count -> \033[0;31m1\033[0m (because a->b\_ptr still holds)
// Memory Leak! No destructors are called.
}
The Solution: Use std::weak_ptr
将其中一个 shared_prt member variable替换为 weak_prt.
A weak_prt observes an object managed by shared_prt but does not increase its reference count.
#include <memory>
class B;
class A {
public:
std: : shared_prt<B> b_prt;
~A( ) { srd: :cout « "A destroyed\n" ; }
} ;
class B (
public:
srd: :weak_prt<A> a_prt; / / Changed to weak_prtl!
-B( ) ( srd: :cout « "B destroyed\n" ; )
} ;
int main( ) (
auto a = srd: : make_ sharedcA>( );
auto b = srd: : make_ sharedcB>( );
a->b\_prt - b;
b->a\_prt - a;
return O;
// Ref-count of 'a' goes from Z -> l, then O -> destroyed.
// When 'a' is destroyed, it releases its hold on 'b'.
// Ref-count of 'b' goes from Z -> l, then O -> destroyed.
}
Why Does This Work?
- When you use a weak pointer, it doesn’t own the object.
- To use the object observed by weak pointer, you must convert it to shared pointer temporarily (using
.lock()method), which increases reference count only for that scope.
void B:useA( ) (
if (auto tmp = a\_prt.lock( )) { / / Creates a temporary shared\_prt
tmp->doSomething( ); / / Safe to use 'tmp' here
} else (
/ / The object 'A' has already been destroyed.
}
}
Are Smart Pointers Thread-Safe?
This question needs to be broken down because different aspects have different safety guarantees.
Thread Safety Rules:
Same Object: Multiple threads can read/destroy DIFFERENT shared_ptrs that point to the SAME object safely.
Example: Copying shared ptr in one thread while another thread destroys its copy - this is fine.Control Block: The internal reference count inside control block is atomic. So copying/destroying shared ptrs pointing to same raw pointer from multiple threads is safe regarding reference counting operations themselves being race-free.
BUT Data Itself Is Not Automatically Protected:
Just like raw pointers, smart pointers don’t automatically synchronize access to the actual object they point at!Unique ptr Has Strict Rules:
Unique ptr should generally NOT be shared between threads because its unique ownership model means only one thread should own/manage it at any time.
Example Showing Lack Of Data Protection:
auto globalPtr = srd: : make_ sharedcint>(O);
void thread_func( )(
for(int i=O;i<100000;++i){
*globalPtr += l; / / DATA RACE!
globalPtr.reset(new int); / / MANAGEMENT RACE! (But control block ops are safe)
}
}
int main(){
srd: :thread tl(thread_func);
srd: :thread tZ(thread_func);
tl.join();
tZ.join();
}
Even though we’re using share pr here and control block operations might be safe individually when resetting or copying etc., there’s absolutely no protection against concurrent modifications of our integer data through dereferencing operator (*).
You still need mutexes or other synchronization primitives around your actual data accesses even when using smart pointers!
内容由零声教学AI助手提供,问题来源于学员提问




