一、为什么需要智能指针
智能指针主要解决的问题有以下内容:
1.内存泄漏:内存手动释放,使用智能指针可以自动释放。 (自动释放内存:智能指针退出作用域,就会在栈上自动析构。先声明的后析构)
2.共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题。
智能指针线程安全的问题
1、ref count是安全的。
2、指向对象数据,如果修改,他是不安全的。(如果要数据安全,要加锁机制)
智能指针是属于栈上的。它的对象是堆上分配的。
- 几个智能指针的特点:
- unique_ptr:独占对象的所有权,由于没有引用计数,因此性能较好。
- shared_ptr:共享对象的所有权,但性能较差。
- weak_ptr:配合shared_ptr,解决循环引用的问题。
二、shared_ptr
1、理解
shared_ptr 使用引用计数,每个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析构的时候,内存才会被释放。
shared_ptr共享管理对象,同一时刻可以多个shared_ptr拥有对象的所有权,当最后一个shared_ptr对象销毁时,被管理对象自动销毁。
简单来说,shared_ptr包含2个部分,
- 一个指向堆上创建的对象的裸指针raw_ptr
- 一个指向内部隐藏的、共享的管理对象。 share_count_object
第一部分没什么好说的,第二部分是重点: - use_count,当前这个堆上对象被多少对象引用了,简单来说就是引用计数。只能count=0才能被析构。
2、基本用法和常用函数
s.get():返回shared_ptr中保存的裸指针
s.reset():重置shared_ptr;
- reset不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针p不是唯一指向该对象的指针,则引用计数减少1,同时p置空。
- reset带参数时,若智能指针s是唯一指向对象的指针,则释放并指向新的对象。若p不是唯一的指针,则只减少引用计数,并指向新的对象。
#include <iostream>
#include <memory> // 包含智能指针的头文件
class MyClass {
public:
MyClass(int value) : value(value) {
std::cout << "MyClass constructed with value: " << value << std::endl;
}
~MyClass() {
std::cout << "MyClass destroyed with value: " << value << std::endl;
}
void Print() const {
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
int main() {
// 创建一个 shared_ptr
std::shared_ptr<MyClass> s = std::make_shared<MyClass>(10);
// 1. 测试不带参数的 reset
std::cout << "--- Testing reset() without arguments ---" << std::endl;
{
std::shared_ptr<MyClass> p = s; // p 和 s 共享所有权
std::cout << "Before reset:" << std::endl;
std::cout << "s use_count: " << s.use_count() << std::endl; // 输出 2
std::cout << "p use_count: " << p.use_count() << std::endl; // 输出 2
p.reset(); // p 释放所有权,s 仍然持有对象
std::cout << "After reset:" << std::endl;
std::cout << "s use_count: " << s.use_count() << std::endl; // 输出 1
std::cout << "p use_count: " << p.use_count() << std::endl; // 输出 0
}
// 2. 测试带参数的 reset
std::cout << "--- Testing reset() with arguments ---" << std::endl;
{
std::shared_ptr<MyClass> p = s; // p 和 s 共享所有权
std::cout << "Before reset:" << std::endl;
std::cout << "s use_count: " << s.use_count() << std::endl; // 输出 2
std::cout << "p use_count: " << p.use_count() << std::endl; // 输出 2
p.reset(new MyClass(20)); // p 指向新对象,s 仍然持有原对象
std::cout << "After reset:" << std::endl;
std::cout << "s use_count: " << s.use_count() << std::endl; // 输出 1
std::cout << "p use_count: " << p.use_count() << std::endl; // 输出 1
p->Print(); // 输出新对象的值
s->Print(); // 输出原对象的值
}
// 3. 测试唯一指针的 reset
std::cout << "--- Testing reset() with unique ownership ---" << std::endl;
{
std::shared_ptr<MyClass> p = std::make_shared<MyClass>(30); // p 是唯一所有者
std::cout << "Before reset:" << std::endl;
std::cout << "p use_count: " << p.use_count() << std::endl; // 输出 1
p.reset(new MyClass(40)); // p 释放原对象,指向新对象
std::cout << "After reset:" << std::endl;
std::cout << "p use_count: " << p.use_count() << std::endl; // 输出 1
p->Print(); // 输出新对象的值
}
return 0;
}
s.use_count():返回shared_ptr的强引用计数。
s.unique():若use_count()为1,返回true,否则false。
2.1、初始化make_shared/reset
通过构造函数、std::shared_ptr辅助函数和reset方法来初始化shared_ptr,代码如下:
shared_ptr<int> p1(new int(1));
shared_ptr<int> p2=p1;
shared_ptr<int> p3;
p3.reset(new int(1));
if(p3){
cout<<"p3 is not null"<<endl;
}
我们应该优先使用make_shared来构造智能指针,因为他更高效。
auto sp1 = make_shared<int>(100);
或
shared_ptr<int>sp1 = make_shared<int>(100);
//相当于
shared_ptr<int> sp1(new int(100));
2.2、当需要获取原始指针时,可以用get()来返回原始指针。
std::shared_ptr<int>p(new int(1));
int* ptr = p.get();
get()会带来的坏处
1、生命周期管理问题:std::shared_ptr的主要作用是自动管理对象的生命周期。当最后一个std::shared_ptr被销毁或重置时,它所管理的对象会被自动释放。然而,p.get()返回的原始指针并不参与引用计数,因此无法保证对象的生命周期。
int main() {
std::shared_ptr<MyClass> p = std::make_shared<MyClass>();
MyClass* rawPtr = p.get(); // 获取原始指针
// 假设在某个地方 p 被重置或销毁
p.reset();
// 此时 rawPtr 指向的对象已经被释放
UseRawPointer(rawPtr); // 未定义行为(悬空指针)
return 0;
}
get()会带来的坏处
2、无法保证线程安全:std::shared_ptr的引用计数是线程安全的,但p.get()返回的原始指针并不提供任何线程安全保障。如果多个线程同时访问或修改原始指针指向的对象,可能会导致数据竞争或未定义行为。
#include <iostream>
#include <memory>
#include <thread>
class MyClass {
public:
int value = 0;
};
void ModifyValue(MyClass* ptr) {
for (int i = 0; i < 100000; ++i) {
ptr->value++; // 数据竞争
}
}
int main() {
std::shared_ptr<MyClass> p = std::make_shared<MyClass>();
MyClass* rawPtr = p.get();
std::thread t1(ModifyValue, rawPtr);
std::thread t2(ModifyValue, rawPtr);
t1.join();
t2.join();
std::cout << "Final value: " << rawPtr->value << std::endl; // 结果不确定
return 0;
}
get()会带来的坏处
3、无法扩展对象的生命周期:.get()返回的原始指针不会增加引用计数,因此无法延长对象的生命周期。如果所有std::shared_ptr都被销毁,即使原始指针仍然存在,对象也会被释放。
#include <iostream>
#include <memory>
class MyClass {
public:
~MyClass() {
std::cout << "MyClass destroyed!" << std::endl;
}
};
int main() {
MyClass* rawPtr = nullptr;
{
std::shared_ptr<MyClass> p = std::make_shared<MyClass>();
rawPtr = p.get(); // 获取原始指针
} // p 超出作用域,对象被释放
// 此时 rawPtr 是悬空指针
std::cout << "Raw pointer still exists, but object is destroyed." << std::endl;
return 0;
}
2.3指定删除器
如果使用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器。
void DeleteIntPtr(int* p){
cout<<"call DeleteIntPtr"<<endl;
delete p;
}
int main(){
shared_ptr<int>p (new int(1), DeleteIntPtr);
return 0;
}
也可以用lambda函数写。
int main(){
shared_ptr<int>p (new int(1), [](int* p){
cout<<"call lambda delete p"<<endl;
delete p;});
return 0;
}
3、需要注意的问题
3.1、不要用原始指针,初始化多个shared_ptr
int * ptr= new int;
shared_ptr<int>p1(ptr);
shared_ptr<int>p2(ptr);//逻辑错误
3.2、不要在函数参数列表中用shared_ptr
因为在不同编译器中,计算顺序是不同的,一般从右到左。如果从左到右,右边出错,而左边写的shared_ptr初始化,就会报错。
fun(shared_ptr<int>(new int),g());
//所以可以改成
shared_ptr<int>p (new int);
fun(p,g());
3.3、通过shared_from_this()返回指针
不要将this指针作为shared_ptr返回出来。因为this指针本质是一个裸指针,因此,这样可能会导致重复析构。
class A{
public:
shared_ptr<A> getSelf(){
return shared_ptr<A>(this);//不能这么做
}
~A(){
cout<<"Destructor A"<<endl;
};
};
int main(){
shared_ptr<A> sp1(new A);
shared_ptr<A>sp2 = sp1->getSelf();
//会打印2次
//Destructor A
//Destructor A
return 0;
}
正确改法:
class A:public std::enable_shared_from_this<A>{
public:
shared_ptr<A> GetSelf(){
return shared_from_this;
}
}
3.4、避免循环引用
循环引用指的是两个或多个对象通过 std::shared_ptr 相互持有对方的引用,形成一个环状依赖关系。由于 std::shared_ptr 的引用计数机制,这些对象的引用计数永远不会降为 0,导致它们无法被正确释放。
#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; // 使用 weak_ptr 代替 shared_ptr
~B() { std::cout << "B destroyed!" << std::endl; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b; // A 持有 B 的 shared_ptr
b->a_ptr = a; // B 持有 A 的 weak_ptr
// 引用计数:
// a 的引用计数:1(只有 a)
// b 的引用计数:2(b 和 a->b_ptr)
// 退出作用域时:
// a 的引用计数降为 0,A 被释放
// b 的引用计数降为 1,但由于 A 被释放,B 的引用计数最终降为 0,B 被释放
return 0;
}
三、unique_ptr
unique_ptr:是一个独占型智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。
unque_ptr<int>p1(new int);
unque_ptr<int>p2 = p1;
unique_ptr不能复制,但是可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他函数的unique_ptr,这样它本身不再拥有原来指针的所有权了。例如:
unique_ptr<T> my_ptr(new T);//
unique_ptr<T>my_other_ptr= std::move(my_ptr);
make_unique是14的性质,不属于11。而make_shared是11。 使用new的版本重复了被创建对象的键入,但是make_unique函数则没有。重复类型违背了软件工程的一个重要原则:应避免重复会引起编译次数增加,导致目标代码膨胀。
- 除了unique_ptr的独占性,与shared_ptr还有一些区别。 可以指向数组,但是shared_ptr不能。
unique_ptr<int []>ptr(new int[9]);//必须指定大小
ptr[9]=8;
shared_ptr<int []>ptr (new int[]);//报错
- unique_ptr指定删除器和shared_ptr有区别。
shared_ptr<int>ptr1 (new int(1),[](int* p){delete p;});//正确
unique_ptr<int>ptr2 (new int(1),[](int* p){delete p;});//错误
shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择。
如果只希望使用一个智能指针管理资源或者数组用unique_ptr,如果希望多个智能指针管理同一个资源就使用shared_ptr。
四、weak_ptr
shared_ptr虽然很好了,但是有内存泄漏问题。当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而内存泄漏。
weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象,进行该对象的内存管理的是那个强引用的shared_ptr,weak_ptr只是提供了对管理对象的一个访问手段。
weak_ptr(弱引用的智能指针)是用来辅助shared_ptr的。
4.1、weak_ptr的基本用法
1、可以使用use_count()获取当前资源的引用计数。
shared_ptr<int> sp(new int(10));
weak_ptr<int>wp(sp);
cout<<wp.use_count<<endl;//结果输出1
2、使用expired()方法判断所观察资源是否已经释放,如下:
shared_ptr<int>sp(new int(10));
weak_ptr<int>wp(sp);
if(wp.expired()){
cout<<"weak_ptr无效,资源已经释放";
}else{
cout<<"weak_ptr有效";
}
3、通过lock获取监视的shared_ptr,如下所示:
std::weak_ptr<int>gw;
void f(){
auto spt = gw.lock();
if(gw.expired()){
cout<<"gw无效,资源已经释放";
}else{
cout<<"gw有效";
}
}
4.2、weak_ptr返回this指针
在C++中,std::weak_ptr 是一种弱引用智能指针,它不会增加所指向对象的引用计数。std::weak_ptr 通常用于解决 std::shared_ptr 的循环引用问题。如果你需要从一个类的成员函数中返回一个指向当前对象的 std::weak_ptr,可以通过 std::shared_from_this 来实现。
使用 std::enable_shared_from_this
为了让一个类能够安全地返回 std::weak_ptr 或 std::shared_ptr 指向自身,该类需要继承 std::enable_shared_from_this。以下是一个示例:
#include <iostream>
#include <memory>
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
std::weak_ptr<MyClass> getWeakPtr() {
// 使用 shared_from_this() 获取当前对象的 shared_ptr,然后转换为 weak_ptr
return weak_from_this();
}
void doSomething() {
std::cout << "Doing something!" << std::endl;
}
};
int main() {
// 创建一个 shared_ptr 指向 MyClass 对象
auto sharedPtr = std::make_shared<MyClass>();
// 获取一个 weak_ptr 指向同一个对象
std::weak_ptr<MyClass> weakPtr = sharedPtr->getWeakPtr();
// 使用 weak_ptr
if (auto lockedPtr = weakPtr.lock()) { // 尝试将 weak_ptr 提升为 shared_ptr
lockedPtr->doSomething(); // 如果对象仍然存在,调用成员函数
} else {
std::cout << "Object has been destroyed!" << std::endl;
}
return 0;
}
4.3、weak_ptr 解决循环引用计数
std::weak_ptr 是一种弱引用,它不会增加对象的引用计数。通过将其中一个 std::shared_ptr 替换为 std::weak_ptr,可以打破循环引用。
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> bPtr;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> aWeakPtr; // 使用 weak_ptr 代替 shared_ptr
~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->bPtr = b; // A 持有 B 的 shared_ptr
b->aWeakPtr = a; // B 持有 A 的 weak_ptr
// 当 main 函数结束时,a 和 b 的引用计数会归零,对象会被正确销毁
return 0;
}
- 当
main函数结束时,a的引用计数变为 0,A对象被销毁。 A对象销毁后,b的引用计数也变为 0,B对象被销毁。- 如果需要访问
std::weak_ptr所指向的对象,可以调用lock()方法将其提升为std::shared_ptr。