std::shared_ptr
是 C++ 标准库 <memory>
头文件中提供的一种智能指针,用于管理动态分配的对象。它采用引用计数的机制来跟踪有多少个 std::shared_ptr
共享同一个对象。每当一个新的 std::shared_ptr
指向该对象时,引用计数加 1;当一个 std::shared_ptr
被销毁或者指向其他对象时,引用计数减 1。当引用计数变为 0 时,对象的内存会被自动释放。
作用
- 自动内存管理 :和
std::unique_ptr
类似,std::shared_ptr
可以在对象不再被使用时自动释放其内存,避免手动调用delete
带来的内存泄漏风险。 - 共享所有权 :多个
std::shared_ptr
可以同时指向同一个对象,它们共享该对象的所有权。这使得在多个地方使用同一个对象时更加方便,无需担心对象的生命周期管理问题。
场景
- 多个对象共享资源 :当多个不同的部分需要访问和使用同一个动态分配的对象时,使用
std::shared_ptr
可以确保资源的正确管理。例如,在一个图形应用程序中,多个图形元素可能共享同一个纹理资源,使用std::shared_ptr
来管理这个纹理对象,当所有图形元素都不再使用该纹理时,纹理对象的内存会被自动释放。 - 在容器中存储可共享的对象 :可以将
std::shared_ptr
存储在标准容器(如std::vector
、std::list
等)中,方便对多个共享对象进行管理。 - 实现对象之间的复杂引用关系 :在一些复杂的程序中,对象之间可能存在相互引用的情况。使用
std::shared_ptr
可以更好地处理这些引用关系,避免悬空指针和内存泄漏。
用法举例
1. 基本使用
c
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
int main() {
// 创建一个 std::shared_ptr 并指向一个新的 MyClass 对象
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
// 创建另一个 std::shared_ptr 并共享同一个对象
std::shared_ptr<MyClass> ptr2 = ptr1;
std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl; // 输出引用计数
ptr2->doSomething();
// 当 ptr1 和 ptr2 都离开作用域时,引用计数变为 0,对象被销毁
return 0;
}
在这个例子中,std::make_shared<MyClass>()
用于创建一个 MyClass
对象,并返回一个 std::shared_ptr
指向该对象。然后将 ptr1
赋值给 ptr2
,此时它们共享同一个 MyClass
对象,引用计数变为 2。use_count()
方法可以返回当前对象的引用计数。当 ptr1
和 ptr2
都离开 main
函数的作用域时,引用计数减为 0,对象的内存被自动释放。
2. 在容器中使用
c
#include <iostream>
#include <memory>
#include <vector>
class MyClass {
public:
MyClass(int value) : data(value) { std::cout << "MyClass constructor with value: " << data << std::endl; }
~MyClass() { std::cout << "MyClass destructor with value: " << data << std::endl; }
int getData() const { return data; }
private:
int data;
};
int main() {
std::vector<std::shared_ptr<MyClass>> vec;
// 向容器中添加元素
vec.push_back(std::make_shared<MyClass>(1));
vec.push_back(std::make_shared<MyClass>(2));
// 访问容器中的元素
for (const auto& ptr : vec) {
std::cout << "Data: " << ptr->getData() << std::endl;
}
// 容器销毁时,所有 shared_ptr 的引用计数减 1
// 当没有其他 shared_ptr 指向这些对象时,对象的内存会被释放
return 0;
}
在这个例子中,std::vector
存储了多个 std::shared_ptr<MyClass>
对象。向容器中添加元素时,使用 std::make_shared
创建新的 MyClass
对象并将其所有权交给 std::shared_ptr
,然后添加到容器中。当容器销毁时,所有 std::shared_ptr
的引用计数减 1,当引用计数变为 0 时,对象的内存会被自动释放。
3. 作为函数参数和返回值
c
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
// 接受一个 std::shared_ptr 作为参数
void processObject(std::shared_ptr<MyClass> obj) {
obj->doSomething();
std::cout << "Inside processObject, use count: " << obj.use_count() << std::endl;
}
// 返回一个 std::shared_ptr
std::shared_ptr<MyClass> createObject() {
return std::make_shared<MyClass>();
}
int main() {
std::shared_ptr<MyClass> obj = createObject();
std::cout << "Before calling processObject, use count: " << obj.use_count() << std::endl;
processObject(obj);
std::cout << "After calling processObject, use count: " << obj.use_count() << std::endl;
return 0;
}
在这个例子中,createObject
函数返回一个 std::shared_ptr
,表示创建并返回一个新的 MyClass
对象的共享所有权。processObject
函数接受一个 std::shared_ptr
作为参数,当调用 processObject(obj)
时,obj
的引用计数加 1。在 processObject
函数内部,引用计数为 2;函数返回后,引用计数减 1 变回 1。当 obj
离开 main
函数的作用域时,引用计数变为 0,对象的内存被自动释放。
注意事项:
1. 循环引用问题
- 问题描述 :当两个或多个对象通过
std::shared_ptr
相互引用时,会形成循环引用,导致引用计数永远不会降为 0,从而造成内存泄漏。 - 示例代码
c
#include <iostream>
#include <memory>
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
return 0;
}
- 解决方案 :使用
std::weak_ptr
来打破循环引用。std::weak_ptr
是一种不增加引用计数的智能指针,它可以观测std::shared_ptr
所管理的对象。修改上述代码如下:
c
#include <iostream>
#include <memory>
class B;
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> a_ptr; // 使用 std::weak_ptr 打破循环引用
~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
return 0;
}
2. 避免手动管理和 std::shared_ptr
混用
- 问题描述 :如果手动管理对象的内存(使用
new
和delete
),同时又使用std::shared_ptr
管理同一个对象,可能会导致对象被多次删除,从而引发未定义行为。 - 示例代码
c
#include <memory>
class MyClass {
public:
~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};
int main() {
MyClass* rawPtr = new MyClass();
std::shared_ptr<MyClass> sharedPtr(rawPtr);
delete rawPtr; // 错误:手动删除已经被 std::shared_ptr 管理的对象
return 0;
}
- 解决方案 :始终使用
std::make_shared
来创建std::shared_ptr
,避免手动使用new
。例如:
c
#include <memory>
class MyClass {
public:
~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};
int main() {
std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
return 0;
}
3. 线程安全问题
- 问题描述 :
std::shared_ptr
的引用计数操作是线程安全的,但对其所管理对象的访问不是线程安全的。如果多个线程同时访问和修改std::shared_ptr
所管理的对象,可能会导致数据竞争和未定义行为。 - 示例代码
c
#include <iostream>
#include <memory>
#include <thread>
class MyClass {
public:
int value = 0;
void increment() { ++value; }
};
void worker(std::shared_ptr<MyClass> obj) {
for (int i = 0; i < 100000; ++i) {
obj->increment();
}
}
int main() {
std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
std::thread t1(worker, obj);
std::thread t2(worker, obj);
t1.join();
t2.join();
std::cout << "Final value: " << obj->value << std::endl;
return 0;
}
- 解决方案 :在多线程环境中,对
std::shared_ptr
所管理的对象的访问需要进行同步。可以使用互斥锁(std::mutex
)来保护对象的访问。修改上述代码如下:
c
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
class MyClass {
public:
int value = 0;
std::mutex mtx;
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++value;
}
};
void worker(std::shared_ptr<MyClass> obj) {
for (int i = 0; i < 100000; ++i) {
obj->increment();
}
}
int main() {
std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
std::thread t1(worker, obj);
std::thread t2(worker, obj);
t1.join();
t2.join();
std::cout << "Final value: " << obj->value << std::endl;
return 0;
}
4. 性能开销
- 问题描述 :
std::shared_ptr
的引用计数操作会带来一定的性能开销,尤其是在频繁创建和销毁std::shared_ptr
的场景下。 - 解决方案 :如果不需要共享所有权,尽量使用
std::unique_ptr
,它的性能开销相对较小。只有在确实需要共享对象所有权时,才使用std::shared_ptr
。
5. 异常安全问题
- 问题描述 :在创建
std::shared_ptr
时,如果发生异常,可能会导致部分资源分配成功而部分失败,从而造成资源泄漏。 - 解决方案 :使用
std::make_shared
来创建std::shared_ptr
,它可以保证在异常发生时不会有资源泄漏。因为std::make_shared
会一次性分配对象和引用计数的内存,要么全部成功,要么全部失败。