提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
std::shared_ptr 是 C++ 标准库中用于管理动态分配对象的智能指针,它采用引用计数机制来实现多个智能指针共享同一个对象的所有权,当引用计数降为 0 时,所管理的对象会被自动释放,从而避免内存泄漏。以下是关于 std::shared_ptr 的详细用法及各个函数说明:
一、使用介绍
1. 包含头文件
要使用 std::shared_ptr,需要包含 头文件:
#include <memory>
2. 创建 std::shared_ptr
2.1 使用构造函数
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};
int main() {
// 使用构造函数创建 std::shared_ptr
std::shared_ptr<MyClass> ptr1(new MyClass());
return 0;
}
这里直接使用 new 运算符创建一个 MyClass 对象,并将其交给 std::shared_ptr 管理。
2.2 使用 std::make_shared
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};
int main() {
// 使用 std::make_shared 创建 std::shared_ptr
std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();
return 0;
}
std::make_shared 是 C++11 引入的一个便捷函数,它可以更高效地创建 std::shared_ptr,避免了不必要的内存分配。
3. 引用计数相关操作
3.1 use_count()
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1;
std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;
return 0;
}
use_count() 函数返回当前 std::shared_ptr 所管理对象的引用计数,即有多少个 std::shared_ptr 共享同一个对象。
3.2 复制和赋值操作
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1; // 复制构造
std::shared_ptr<MyClass> ptr3;
ptr3 = ptr1; // 赋值操作
std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;
std::cout << "ptr3 use count: " << ptr3.use_count() << std::endl;
return 0;
}
复制构造和赋值操作会增加引用计数,当 std::shared_ptr 被销毁或重置时,引用计数会减少。
4. 访问所管理的对象
4.1 解引用运算符 *
#include <iostream>
#include <memory>
class MyClass {
public:
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
(*ptr).doSomething();
return 0;
}
使用解引用运算符 * 可以访问 std::shared_ptr 所管理的对象。
4.2 箭头运算符 ->
#include <iostream>
#include <memory>
class MyClass {
public:
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
ptr->doSomething();
return 0;
}
箭头运算符 -> 可以直接访问所管理对象的成员函数或成员变量。
5. 重置 std::shared_ptr
5.1 reset()
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1;
std::cout << "Before reset, ptr1 use count: " << ptr1.use_count() << std::endl;
ptr1.reset();
std::cout << "After reset, ptr1 use count: " << ptr1.use_count() << std::endl;
std::cout << "After reset, ptr2 use count: " << ptr2.use_count() << std::endl;
return 0;
}
reset() 函数会减少当前 std::shared_ptr 的引用计数,如果引用计数降为 0,则释放所管理的对象。调用 reset() 后,该 std::shared_ptr 会变为空指针。
5.2 reset(new T())
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
ptr.reset(new MyClass());
return 0;
}
reset(new T()) 会先减少当前 std::shared_ptr 的引用计数,如果引用计数降为 0,则释放所管理的对象,然后让该 std::shared_ptr 管理一个新创建的对象。
6. 获取原始指针
get()
#include <iostream>
#include <memory>
class MyClass {
public:
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
MyClass* rawPtr = ptr.get();
rawPtr->doSomething();
return 0;
}
get() 函数返回 std::shared_ptr 所管理对象的原始指针。需要注意的是,使用原始指针时要小心,避免在 std::shared_ptr 释放对象后继续使用该指针,否则会导致未定义行为。
7. 自定义删除器
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};
void customDeleter(MyClass* obj) {
std::cout << "Custom deleter called" << std::endl;
delete obj;
}
int main() {
std::shared_ptr<MyClass> ptr(new MyClass(), customDeleter);
return 0;
}
std::shared_ptr 允许使用自定义删除器,当引用计数降为 0 时,会调用自定义删除器来释放对象,而不是使用默认的 delete 操作符。
综上所述,std::shared_ptr 提供了方便的内存管理功能,通过引用计数机制实现多个智能指针共享同一个对象的所有权。在使用时,要注意避免循环引用问题,以免导致内存泄漏。
二、shared_ptr 如何避免循环引用
在 C++ 中,std::shared_ptr 采用引用计数的方式来管理对象的生命周期,多个 std::shared_ptr 可以共享同一个对象,当引用计数降为 0 时,对象会被自动释放。然而,循环引用会导致引用计数永远无法降为 0,从而造成内存泄漏。以下详细介绍循环引用的产生原因以及避免循环引用的方法。
1. 循环引用的产生原因
当两个或多个对象通过 std::shared_ptr 相互引用时,就会形成循环引用。例如,有两个类 A 和 B,它们各自持有对方的 std::shared_ptr,示例代码如下:
#include <iostream>
#include <memory>
class B;
class A {
public:
std::shared_ptr<B> bPtr;
A() { std::cout << "A constructor" << std::endl; }
~A() { std::cout << "A destructor" << std::endl; }
};
class B {
public:
std::shared_ptr<A> aPtr;
B() { std::cout << "B constructor" << std::endl; }
~B() { std::cout << "B destructor" << std::endl; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->bPtr = b;
b->aPtr = a;
return 0;
}
在上述代码中,A 对象持有一个指向 B 对象的 std::shared_ptr,B 对象也持有一个指向 A 对象的 std::shared_ptr。当 main 函数结束时,a 和 b 超出作用域,它们的引用计数减 1,但由于彼此之间的相互引用,引用计数仍然不为 0,因此 A 和 B 对象不会被销毁,从而造成内存泄漏。
2. 避免循环引用的方法
2.1 使用 std::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 constructor" << std::endl; }
~A() { std::cout << "A destructor" << std::endl; }
};
class B {
public:
std::weak_ptr<A> aPtr; // 使用 std::weak_ptr 避免循环引用
B() { std::cout << "B constructor" << std::endl; }
~B() { std::cout << "B destructor" << std::endl; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->bPtr = b;
b->aPtr = a;
return 0;
}
在这个修改后的代码中,B 类中的 aPtr 被改为 std::weak_ptr。当 main 函数结束时,a 和 b 超出作用域,它们的引用计数减 1,由于 b->aPtr 是弱引用,不影响 A 对象的引用计数,因此 A 对象的引用计数降为 0,A 对象被销毁。A 对象销毁后,B 对象的引用计数也降为 0,B 对象也会被销毁,从而避免了内存泄漏。
2.2 std::weak_ptr 的使用方法
std::weak_ptr 不能直接访问所引用的对象,需要先将其转换为 std::shared_ptr 才能访问。可以使用 lock() 函数来实现转换,示例代码如下:
#include <iostream>
#include <memory>
class B;
class A {
public:
std::shared_ptr<B> bPtr;
A() { std::cout << "A constructor" << std::endl; }
~A() { std::cout << "A destructor" << std::endl; }
};
class B {
public:
std::weak_ptr<A> aPtr;
void accessA() {
std::shared_ptr<A> sharedA = aPtr.lock();
if (sharedA) {
std::cout << "Accessing A from B" << std::endl;
} else {
std::cout << "A has been destroyed" << std::endl;
}
}
B() { std::cout << "B constructor" << std::endl; }
~B() { std::cout << "B destructor" << std::endl; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->bPtr = b;
b->aPtr = a;
b->accessA();
return 0;
}
在 B 类的 accessA() 方法中,使用 aPtr.lock() 将 std::weak_ptr 转换为 std::shared_ptr,如果所引用的对象还存在,则转换成功,可以通过返回的 std::shared_ptr 访问对象;如果对象已经被销毁,则 lock() 函数返回一个空的 std::shared_ptr。
通过使用 std::weak_ptr,可以有效地避免 std::shared_ptr 之间的循环引用问题,确保对象在不再被使用时能够正确释放内存。
三、shared_ptr 和 unique_ptr 有什么区别
std::shared_ptr 和 std::unique_ptr 都是 C++ 标准库 中用于管理动态分配内存的智能指针,它们的设计目的是帮助开发者避免手动管理内存时可能出现的内存泄漏和悬空指针等问题,但二者在所有权语义、性能、使用场景等方面存在显著区别,以下是详细对比:
1. 所有权语义
std::unique_ptr
独占所有权:std::unique_ptr 对其所指向的对象拥有独占所有权,即同一时间只能有一个 std::unique_ptr 指向该对象。当这个 std::unique_ptr 被销毁或者重置时,它所管理的对象也会被自动销毁。
示例代码:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};
int main() {
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
// std::unique_ptr<MyClass> ptr2 = ptr1; // 编译错误,不能直接拷贝
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 可以通过移动语义转移所有权
if (!ptr1) {
std::cout << "ptr1 is now empty." << std::endl;
}
return 0;
}
std::shared_ptr
共享所有权:多个 std::shared_ptr 可以同时指向同一个对象,它们通过引用计数机制来管理对象的生命周期。每个 std::shared_ptr 都会维护一个引用计数,记录有多少个 std::shared_ptr 共享该对象。当引用计数变为 0 时,对象会被自动销毁。
示例代码:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1; // 可以拷贝,引用计数加 1
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
return 0;
}
2. 性能差异
std::unique_ptr
轻量级:由于 std::unique_ptr 是独占所有权,不需要维护引用计数,所以它的实现通常比 std::shared_ptr 更轻量级,开销更小。创建、销毁和转移所有权的操作都非常高效。
std::shared_ptr
引用计数开销:std::shared_ptr 需要维护引用计数,这涉及到额外的内存分配和原子操作(为了保证线程安全),因此在频繁进行所有权转移和共享的场景下,会带来一定的性能开销。
3. 所有权转移方式
std::unique_ptr
移动语义:std::unique_ptr 的所有权转移只能通过移动语义(std::move)来实现。移动后,原 std::unique_ptr 会失去对对象的所有权,变为空指针。
std::shared_ptr
拷贝和赋值:std::shared_ptr 可以通过拷贝构造函数和赋值运算符进行所有权共享,每次拷贝或赋值操作都会增加引用计数。
4. 使用场景
std::unique_ptr
明确独占关系:适用于对象的所有权明确,且不需要共享的场景。例如,在函数内部管理局部对象的生命周期,或者一个类独占某个资源时使用。
作为函数返回值:常用于函数返回动态分配的对象,避免内存泄漏。
#include <memory>
std::unique_ptr<int> createInt() {
return std::make_unique<int>(42);
}
std::shared_ptr
多对象共享:适用于多个地方需要访问同一个对象,并且对象的生命周期需要由多个部分共同管理的场景。例如,在多线程环境中,多个线程可能需要同时访问同一个对象,此时可以使用 std::shared_ptr 来确保对象在所有线程都不再使用时才被释放。
容器存储:当需要将多个指向同一对象的指针存储在容器中时,std::shared_ptr 是一个合适的选择。
#include <memory>
#include <vector>
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(42);
std::vector<std::shared_ptr<int>> vec;
vec.push_back(ptr);
return 0;
}
综上所述,std::unique_ptr 和 std::shared_ptr 在不同的场景下各有优势,开发者应根据具体需求选择合适的智能指针。
总结
以上就是今天要讲的内容,本文仅仅简单介绍了智能指针share_ptr的使用,后续项目开发中可以借鉴参考。