auto_ptr:
同一个对象只可被一个auto_ptr拥有
- 复制的时候可能会出现问题,所以auto_ptr的复制是转移对象拥有权
- 被转移的auto_ptr又有可能在析构的时候删除根本不存在的对象,即访问无效内存,所以要在析构函数中加一层是否还拥有原来指向对象的指针的判断
- 在复制和赋值的时候改变了参数的内容,换句话说,如果auto_ptr被复制,它们会被修改。传统的复制和赋值通常需要用const,但是这里不行
unique_ptr:
unique_ptr也使用独占所有权的方式,但是禁止了拷贝构造和拷贝赋值操作,并且可以自定义删除器。
结论:
由于 auto_ptr
允许拷贝构造和拷贝赋值操作,这导致在某些情况下,所有权的转移并不直观,容易引发潜在的资源泄漏或访问无效内存的问题。此外,auto_ptr
不支持自定义删除器,限制了其灵活性。
在 std::shared_ptr
中,实际的值是存储在动态分配的堆内存上的。
std::shared_ptr
通过使用引用计数技术来管理共享资源的所有权。它包含两个部分:控制块(control block)和指向堆内存的指针。
控制块是一个数据结构,它存储了引用计数和其他与 std::shared_ptr
相关的信息。它通常会在堆上分配,并由 std::shared_ptr 对象共享。控制块中的引用计数跟踪有多少个 std::shared_ptr
对象共享同一个资源。
当有新的 std::shared_ptr
对象指向相同的资源时,它们会共享同一个控制块和指向堆内存的指针。引用计数会相应增加,确保资源只在最后一个 std::shared_ptr
对象销毁时释放。
当引用计数降为零时,即所有的 std::shared_ptr
对象都销毁时,控制块会负责释放堆内存和自身的内存。
实际的值是存储在指向堆内存的指针所指向的位置。而 std::shared_ptr
对象本身只是包装了控制块和指向堆内存的指针,用于管理资源的共享和释放。
_Uses
、_Weaks
和 _vfptr
是 shared_ptr
内部实现的一些成员变量。
_Uses
是一个指向引用计数控制块的指针。该控制块存储了引用计数和其他与shared_ptr
相关的信息。每个shared_ptr
对象都共享同一个_Uses
指针,通过该指针可以更新引用计数。_Weaks
是一个指向弱引用计数控制块的指针。弱引用是一种不增加引用计数的指针,用于监测对象是否已被销毁。_Weaks
存储了与弱引用相关的信息,包括弱引用计数。当弱引用计数为零时,表示没有任何weak_ptr
指向对象,可以安全地销毁对象。_vfptr
是虚函数指针,用于实现shared_ptr
的多态性。shared_ptr
是一个模板类,因此_vfptr
指向了一个虚函数表,包含了shared_ptr
的虚函数。通过_vfptr
,shared_ptr
可以在运行时根据实际对象类型调用适当的函数。
这些成员变量是 shared_ptr
内部实现的细节,对于一般的使用场景来说,不需要直接操作这些成员变量。相反,应该使用 shared_ptr
提供的公共接口来管理资源和访问对象。这样可以确保正确的引用计数和资源管理,而无需关心内部实现细节。
在《more effective c++》中
-
目标是建造一个引用计数的string类
-
以RCObject管理refCount,让StringValue继承RCObject,同时里面用char*指针存储数据,实现表现字符串实值。
-
为了实现多个等值对象共享同一个实值,再创建智能指针RCPtr,指向StringValue,让string通过管理智能指针的方法实现引用计数的加减。
- weak_ptr不会增加shared_ptr的引用计数,可以破解循环引用
- weak_ptr可以用它的成员函数lock安全的得到shared_ptr,以判断shared_ptr是否还有效
下面是一个示例程序,演示如何使用 std::weak_ptr
和 std::shared_ptr
相互配合,实现弱引用和共享引用的功能:
#include <iostream>
#include <memory>
struct MyObject {
int value;
MyObject(int val) : value(val) {
std::cout << "Constructing MyObject with value: " << value << std::endl;
}
~MyObject() {
std::cout << "Destructing MyObject with value: " << value << std::endl;
}
};
int main() {
std::shared_ptr<MyObject> sharedPtr = std::make_shared<MyObject>(42);
std::weak_ptr<MyObject> weakPtr(sharedPtr);
// 使用 shared_ptr 访问对象
if (std::shared_ptr<MyObject> shared = weakPtr.lock()) {
std::cout << "Weak pointer value: " << shared->value << std::endl;
}
else {
std::cout << "Object is no longer available." << std::endl;
}
// 释放 shared_ptr
sharedPtr.reset();
// 使用 weak_ptr 访问对象
if (std::shared_ptr<MyObject> shared = weakPtr.lock()) {
std::cout << "Weak pointer value: " << shared->value << std::endl;
}
else {
std::cout << "Object is no longer available." << std::endl;
}
return 0;
}
在上述示例中,我们首先创建了一个 std::shared_ptr
对象 sharedPtr
,它指向一个 MyObject
对象。
然后,我们使用 std::weak_ptr
创建了一个弱引用 weakPtr
,它与 sharedPtr
共享相同的资源。
我们使用 weakPtr
的 lock()
成员函数来尝试获取一个有效的 std::shared_ptr
对象,以访问所指向的对象。如果返回的 shared_ptr
对象不为空,则表示对象仍然存在,我们可以使用该 shared_ptr
访问对象的成员。
接着,我们通过 sharedPtr.reset()
将 sharedPtr
释放,使其引用计数降为零,从而销毁 MyObject
对象。
最后,我们再次使用 weakPtr
的 lock()
成员函数来尝试获取 shared_ptr
,但由于对象已经被销毁,返回的 shared_ptr
对象为空。
通过上述示例,我们展示了如何使用 std::weak_ptr
和 std::shared_ptr
相互配合,实现弱引用的功能,并在需要时安全地访问共享对象。
在 C++ 中,内存泄漏是一个常见问题。内存泄漏通常发生在一段内存被分配(比如通过 new 操作符)但永远不会被释放(比如通过 delete 操作符)。在某些情况下,尤其是在涉及循环引用的情况下,内存泄漏就可能发生。
先来看看什么是循环引用:两个或多个对象相互持有对方的引用,形成一个闭环。即使外部没有任何对这些对象的引用,它们也不能被正确的析构和删除,因为它们彼此间保持着对方的引用,形成一个闭环。例如:
class Object {
public:
std::shared_ptr<Object> partner;
};
void createCycle() {
auto obj1 = std::make_shared<Object>();
auto obj2 = std::make_shared<Object>();
obj1->partner = obj2;
obj2->partner = obj1;
}
在 createCycle 函数中,obj1 和 obj2 形成了一个循环引用。即使 createCycle 函数执行完毕后,obj1 和 obj2 都离开了作用域,obj1 和 obj2的引用计数还仍为1,所以他们的析构函数也不会被调用,因为它们仍然被对方引用着。结果就是这两个对象永远不会被释放,导致内存泄漏。
这时,我们可以通过弱引用来打破这种循环引用,避免内存泄漏。弱引用不会增加对象的引用计数,所以不会阻止对象的析构和删除。下面是使用弱引用避免循环引用的例子:
class Object {
public:
std::weak_ptr<Object> partner;
};
void createCycle() {
auto obj1 = std::make_shared<Object>();
auto obj2 = std::make_shared<Object>();
obj1->partner = obj2;
obj2->partner = obj1;
}
在这个例子中,我们用 std::weak_ptr 代替了 std::shared_ptr。现在,即使 obj1 和 obj2 互相引用,它们也会在 createCycle 函数结束时被正确地析构和删除,从而避免了内存泄漏。
和其他对象共享一份实值,直到我们必须对自己所拥有的那一份实值进行写动作