• std::weak_ptr 的主要作用 是提供一种不影响引用计数的弱引用,解决循环引用问题,并提供安全的弱引用访问方式。
• 控制块中的引用计数和弱引用计数 分别用于管理 std::shared_ptr 和 std::weak_ptr 的生命周期。
• 当 std::shared_ptr 的引用计数降为零 时,管理的对象会被销毁,但控制块会继续存在,直到所有 std::weak_ptr 也被销毁。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr;
sharedPtr.reset(); // 销毁资源
if (auto sp = weakPtr.lock()) {
std::cout << "Resource is available: " << *sp << std::endl;
} else {
std::cout << "Resource has been destroyed, but control block still exists" << std::endl;
}
return 0;
}
1. 创建 std::shared_ptr
可以使用 std::make_shared 或直接构造 std::shared_ptr 来创建智能指针。
#include <iostream>
#include <memory>
int main() {
// 使用 std::make_shared 创建
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
// 直接构造 std::shared_ptr
std::shared_ptr<int> ptr2(new int(20));
// 检查指针是否为空
if (ptr1) {
std::cout << "ptr1 points to " << *ptr1 << std::endl;
}
if (ptr2) {
std::cout << "ptr2 points to " << *ptr2 << std::endl;
}
return 0;
}
1.2 注意事项
1.2.1 不要用一个原始指针初始化多个shared_ptr
int* ptr =new int();
std::shared_prt<int> p1(ptr);
std::shared_prt<int> p2(ptr);
1.2.2 2 不要在函数实参中创建shared_ptr
functionA(shared_ptr<int>(new int), g());
2. 访问和操作对象
过 operator* 和 operator-> 可以访问 std::shared_ptr 所管理的对象
#include <iostream>
#include <memory>
class MyClass {
public:
void display() const {
std::cout << "MyClass::display()" << std::endl;
}
};
int main() {
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
// 使用 operator->
ptr->display();
// 使用 operator*
(*ptr).display();
return 0;
}
3. 复制和赋值
std::shared_ptr 可以被复制和赋值,复制后的指针将共享同一个对象。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(30);
std::shared_ptr<int> ptr2 = ptr1; // 复制指针
std::cout << "ptr1 points to " << *ptr1 << std::endl;
std::cout << "ptr2 points to " << *ptr2 << std::endl;
// 检查引用计数
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
return 0;
}
4. 重置 std::shared_ptr
可以使用 reset() 方法重置 std::shared_ptr,从而释放其管理的对象.
std::shared_ptr 提供了 reset() 方法,用于重置智能指针的管理对象。重置的行为取决于 reset() 是否带参数,以及该智能指针是否被多个智能指针共享
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(40);
std::cout << "ptr points to " << *ptr << std::endl;
// 重置指针
ptr.reset();
// 检查指针是否为空
if (!ptr) {
std::cout << "ptr is now empty" << std::endl;
}
return 0;
}
4.1reset() 不带参数
当 reset() 不带参数时,智能指针将释放其当前管理的对象(如果是唯一持有者),并将其设置为空指针
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(42);
std::cout << "Before reset: " << (ptr ? "not empty" : "empty") << std::endl;
ptr.reset(); // 不带参数的 reset,将 ptr 设为空指针
std::cout << "After reset: " << (ptr ? "not empty" : "empty") << std::endl;
return 0;
}
4.2 reset() 带参数
当 reset() 带参数时,智能指针将释放其当前管理的对象(如果是唯一持有者),并开始管理新的对象。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(42);
std::cout << "Before reset: " << *ptr << std::endl;
ptr.reset(new int(100)); // 带参数的 reset,ptr 现在管理新的对象
std::cout << "After reset: " << *ptr << std::endl;
return 0;
}
4.3 被多个智能指针管理的情形
当一个对象被多个 std::shared_ptr 管理时,只有当最后一个 std::shared_ptr 被销毁或重置时,才会释放对象。
#include <iostream>
#include <memory>
void print_ref_count(const std::shared_ptr<int>& ptr) {
std::cout << "Reference count: " << ptr.use_count() << std::endl;
}
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
print_ref_count(ptr1); // 引用计数为 1
{
std::shared_ptr<int> ptr2 = ptr1; // 复制 ptr1,引用计数加 1
print_ref_count(ptr1); // 引用计数为 2
ptr1.reset(); // ptr1 被重置,但对象仍被 ptr2 管理
print_ref_count(ptr2); // 引用计数为 1
}
// 作用域结束,ptr2 被销毁,对象引用计数为 0,被释放
std::cout << "ptr1 is " << (ptr1 ? "not empty" : "empty") << std::endl;
return 0;
}
4.4 带参数的 reset() 在共享对象时的行为
#include <iostream>
#include <memory>
void print_ref_count(const std::shared_ptr<int>& ptr) {
std::cout << "Reference count: " << ptr.use_count() << std::endl;
}
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
print_ref_count(ptr1); // 引用计数为 1
{
std::shared_ptr<int> ptr2 = ptr1; // 复制 ptr1,引用计数加 1
print_ref_count(ptr1); // 引用计数为 2
ptr1.reset(new int(100)); // ptr1 现在管理一个新的对象
print_ref_count(ptr2); // 原对象引用计数为 1
print_ref_count(ptr1); // 新对象引用计数为 1
}
// 作用域结束,ptr2 被销毁,原对象引用计数为 0,被释放
std::cout << "ptr1 points to " << *ptr1 << std::endl;
return 0;
}
在上述示例中:
1. ptr1 和 ptr2 共享同一个对象。
2. 当 ptr1 使用带参数的 reset(new int(100)) 时,ptr1 开始管理一个新的对象,而原对象仍然由 ptr2 管理。
3. 当 ptr2 超出作用域被销毁时,原对象的引用计数降为 0,被释放。
4. ptr1 现在管理一个新的对象,其引用计数为 1。
5. 获取原始指针
可以使用 get() 方法获取 std::shared_ptr 所管理的原始指针,但要小心使用,避免管理问题。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(50);
// 获取原始指针
int* raw_ptr = ptr.get();
std::cout << "Raw pointer points to " << *raw_ptr << std::endl;
return 0;
}
6. 使用自定义删除器
#include <iostream>
#include <memory>
void custom_deleter(int* ptr) {
std::cout << "Custom deleter called" << std::endl;
delete ptr;
}
int main() {
std::shared_ptr<int> ptr(new int(60), custom_deleter);
std::cout << "ptr points to " << *ptr << std::endl;
return 0;
}
7. 循环引用问题
7.1 问题的产生
a 和 b 互相引用,导致它们的引用计数都不为 0,即使在 main 函数结束时,a 和 b 也不会被销毁,从而导致内存泄漏
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> ptrB;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::shared_ptr<A> ptrA;
~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->ptrB = b;
b->ptrA = a;
// 此时 a 和 b 的引用计数都不为 0,导致内存泄漏
return 0;
}
7.2 解决方案:使用 std::weak_ptr
可以使用 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> ptrB;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> ptrA; // 使用 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->ptrB = b;
b->ptrA = a; // 使用 weak_ptr 避免循环引用
return 0;
}
7.3 如何使用 std::weak_ptr
7.3.1 创建 std::weak_ptr
std::weak_ptr 可以从 std::shared_ptr 创建
std::shared_ptr<A> a = std::make_shared<A>();
std::weak_ptr<A> weak_a = a;
7.3.2 锁定 std::weak_ptr
std::weak_ptr 本身不能直接访问对象,需要通过 lock() 方法将其转换为 std::shared_ptr。如果对象已被销毁,lock() 返回一个空的 std::shared_ptr
if (auto shared_a = weak_a.lock()) {
// 可以安全地使用 shared_a
shared_a->some_method();
} else {
// 对象已被销毁
}
7.3.3 实例
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> ptrB;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> ptrA; // 使用 std::weak_ptr 解决循环引用
~B() { std::cout << "B destroyed" << std::endl; }
void useA() {
if (auto a = ptrA.lock()) {
std::cout << "Using A" << std::endl;
} else {
std::cout << "A is no longer available" << std::endl;
}
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->ptrB = b;
b->ptrA = a; // 使用 weak_ptr 避免循环引用
b->useA(); // 使用 A
return 0;
}
7.3.4 expired()
weak_ptr<int> wp;
shared_ptr<int> sp_ok;
{
shared_ptr<int> sp(new int(1)); //sp.use_count()==1
wp = sp; //wp不会改变引用计数,所以sp.use_count()==1
sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
}
if(wp.expired()) {
cout << "shared_ptr is destroy" << endl;
} else {
cout << "shared_ptr no destroy" << endl;
}
8.别名构造函数
8.1 基本概念
std::shared_ptr 的别名构造函数允许你创建一个新 std::shared_ptr,它共享另一个 std::shared_ptr 的所有权(即共享引用计数),但指向不同的对象。这可以用于以下几种情况:
• 管理一个对象的子对象或成员。
• 在容器或复合对象中共享控制块,但指向不同的元素。
8.2 别名构造函数的使用
template<class Y, class T>
shared_ptr(const shared_ptr<Y>& r, T* p) noexcept;
• r 是一个已有的 std::shared_ptr,它管理某个对象。
• p 是一个原始指针,指向要管理的不同对象。
新创建的 std::shared_ptr 将共享 r 的控制块(包括引用计数),但指向 p 所指向的对象。
8.3 示例
8.3.1 管理对象的成员
#include <iostream>
#include <memory>
struct Base {
int value;
Base(int v) : value(v) {}
};
struct Derived : public Base {
Derived(int v) : Base(v) {}
};
int main() {
// 创建一个 std::shared_ptr 管理 Derived 对象
std::shared_ptr<Derived> derivedPtr = std::make_shared<Derived>(100);
// 使用别名构造函数创建一个 std::shared_ptr 管理 Base 子对象
std::shared_ptr<Base> basePtr(derivedPtr, derivedPtr.get());
std::cout << "Derived value: " << derivedPtr->value << std::endl;
std::cout << "Base value: " << basePtr->value << std::endl;
std::cout << "Derived reference count: " << derivedPtr.use_count() << std::endl;
std::cout << "Base reference count: " << basePtr.use_count() << std::endl;
return 0;
}
在这个示例中:
1. derivedPtr 是一个管理 Derived 对象的 std::shared_ptr。
2. basePtr 使用别名构造函数创建,管理 Derived 对象的 Base 部分。
3. derivedPtr 和 basePtr 共享同一个控制块,引用计数为 2。
8.3.2 管理对象的成员
#include <iostream>
#include <memory>
struct MyStruct {
int x;
int y;
MyStruct(int a, int b) : x(a), y(b) {}
};
int main() {
// 创建一个 std::shared_ptr 管理 MyStruct 对象
std::shared_ptr<MyStruct> structPtr = std::make_shared<MyStruct>(10, 20);
// 使用别名构造函数创建 std::shared_ptr 仅管理 x 成员
std::shared_ptr<int> xPtr(structPtr, &structPtr->x);
// 使用别名构造函数创建 std::shared_ptr 仅管理 y 成员
std::shared_ptr<int> yPtr(structPtr, &structPtr->y);
std::cout << "structPtr x: " << structPtr->x << ", y: " << structPtr->y << std::endl;
std::cout << "xPtr: " << *xPtr << std::endl;
std::cout << "yPtr: " << *yPtr << std::endl;
std::cout << "structPtr reference count: " << structPtr.use_count() << std::endl;
std::cout << "xPtr reference count: " << xPtr.use_count() << std::endl;
std::cout << "yPtr reference count: " << yPtr.use_count() << std::endl;
return 0;
}
1. structPtr 是一个管理 MyStruct 对象的 std::shared_ptr。
2. xPtr 使用别名构造函数创建,管理 MyStruct 对象的 x 成员。
3. yPtr 使用别名构造函数创建,管理 MyStruct 对象的 y 成员。
4. structPtr、xPtr 和 yPtr 共享同一个控制块,引用计数为 3。
9.shared_from_this
9.1 作用
shared_from_this 的主要作用是允许对象安全地创建一个 std::shared_ptr 指向自身,特别是在对象的成员函数中。它解决了直接使用 this 指针可能导致的问题,例如对象提前销毁或多重所有权的问题。
9.2 使用方法
要使用 shared_from_this,类需要继承 std::enable_shared_from_this<T>,其中 T 是类的类型。这样,类的实例就可以调用 shared_from_this() 方法来生成一个 std::shared_ptr 指向自身。
9.3 示例
#include <iostream>
#include <memory>
using namespace std;
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
void show() {
std::shared_ptr<MyClass> self = shared_from_this();
std::cout << "MyClass instance at: " << this << ", use count: " << self.use_count() << std::endl;
}
~MyClass() {
std::cout << "MyClass destructor called for: " << this << std::endl;
}
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
ptr1->show();
std::cout << "Use count after show() called: " << ptr1.use_count() << std::endl;
std::shared_ptr<MyClass> ptr2 = ptr1->shared_from_this();
std::cout << "Use count after shared_from_this: " << ptr1.use_count() << std::endl;
return 0;
}
ptr2 是通过 shared_from_this() 获取的另一个指向同一对象的 std::shared_ptr,引用计数正确更新。
MyClass instance at: 0x1bd0e80, use count: 2
--> 调用 ptr1->show() 时,shared_from_this() 返回一个新的 std::shared_ptr,增加了引用计数。
Use count after show() called: 1
--> show()函数体结束。
Use count after shared_from_this: 2
MyClass destructor called for: 0x1bd0e80
9.4 注意事项
9.4.1. 必须通过 std::shared_ptr 创建对象
对象必须通过 std::shared_ptr 创建,才能正确使用 shared_from_this()。否则,会导致未定义行为。错误示例:MyClass obj;
std::shared_ptr<MyClass> sp = obj.shared_from_this(); // 未定义行为正确示例:std::shared_ptr<MyClass> sp = std::make_shared<MyClass>();
std::shared_ptr<MyClass> sp2 = sp->shared_from_this(); // 正确
9.4.2. 避免在构造函数和析构函数中使用 shared_from_this
在对象的构造函数和析构函数中调用 shared_from_this() 是未定义行为,因为在这些时候,std::shared_ptr 可能还没有完全构建或已经销毁。错误示例:class MyClass : public std::enable_shared_from_this<MyClass> {
public:
MyClass() {
std::shared_ptr<MyClass> self = shared_from_this(); // 未定义行为
}
};
9.4.3. 正确使用 shared_from_this
在需要从成员函数中获取 std::shared_ptr 指向当前对象时,使用 shared_from_this 是一个好的选择,可以确保对象的生命周期管理。正确示例:void MyClass::someMemberFunction() {
std::shared_ptr<MyClass> self = shared_from_this();
// 安全地使用 self
}
10.std::shared_ptr 在某些方面是线程安全的,但在其他方面则不是
10.1 引用计数是线程安全的
std::shared_ptr 的引用计数操作是线程安全的。这意味着多个线程可以同时持有并操作同一个 std::shared_ptr 实例,而不需要额外的同步机制。例如,当一个 std::shared_ptr 被复制或销毁时,引用计数会自动更新,并且这个更新操作是线程安全的。
#include <iostream>
#include <memory>
#include <thread>
void threadFunc(std::shared_ptr<int> ptr) {
std::cout << "In thread: " << *ptr << std::endl;
}
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(42);
std::thread t1(threadFunc, ptr);
std::thread t2(threadFunc, ptr);
t1.join();
t2.join();
return 0;
}
在这个示例中,两个线程同时持有并使用同一个 std::shared_ptr,不会产生竞争条件,因为引用计数操作是线程安全的。
10.2 对象操作和指针本身的读写不是线程安全的
虽然 std::shared_ptr 的引用计数是线程安全的,但它所管理的对象的操作和指针本身的读写并不是线程安全的。如果多个线程需要同时读写对象或修改指针,必须使用额外的同步机制(如互斥锁 std::mutex)来保护这些操作。
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
std::mutex mtx;
void threadFunc(std::shared_ptr<int> ptr) {
std::lock_guard<std::mutex> lock(mtx);
*ptr += 1;
std::cout << "In thread: " << *ptr << std::endl;
}
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(42);
std::thread t1(threadFunc, ptr);
std::thread t2(threadFunc, ptr);
t1.join();
t2.join();
std::cout << "Final value: " << *ptr << std::endl;
return 0;
}
在这个示例中,std::mutex 被用来保护对共享对象的修改操作,以避免数据竞争。