目录
1、前言
智能指针是代理模式的具体应用,它使用 RAII 技术代理了裸指针,能够自动释放内存, 无需程序员干预,所以被称为“智能指针”。
智能指针不是指针,而是一个对象,所以不要对其调用delete,它会自动管理初始化时的指针,在离开作用域时析构释放内存。
智能指针也没有定义加减运算,不能随意移动指针地址,这样避免了指针越界操作。
在使用上:
如果指针是“独占”使用,就应该选择 unique_ptr,它为裸指针添加了很多限制,更加安全 。
如果指针是“共享”使用,就应该选择 shared_ptr,它的功能非常完善,用法几乎与原始指针一样
使用智能指针要加头文件#include <memory>
工厂函数make_unique()、make_shared()不只是返回智能指针对象,其内部也有优化。
scope_ptr
scoped_ptr是一个智能指针的类模板,它在C++中用于管理动态分配的对象。scoped_ptr提供了一种简单的方式来确保在离开作用域时自动释放内存,并避免内存泄漏。
以下是使用scoped_ptr的基本方法:
- 包含头文件:首先,需要包含
<memory>头文件,因为scoped_ptr是位于该头文件中的。
#include <memory>
- 创建和初始化
scoped_ptr:可以使用scoped_ptr的构造函数来创建并初始化一个指向对象的指针。
std::scoped_ptr<int> ptr(new int(42));
上述代码示例创建了一个 scoped_ptr 对象 ptr,它指向一个动态分配的整数对象并初始化为 42。
- 使用指针:可以通过
scoped_ptr对象访问指向的对象。可以使用解引用操作符*来获取指针所指向的值,也可以使用箭头操作符->来调用指针所指向对象的成员函数。
int value = *ptr;
ptr->someFunction();
上述代码示例展示了如何通过 scoped_ptr 访问指向的对象。可以将指针解引用以获取值,并使用箭头操作符调用对象的成员函数。
- 释放内存:
scoped_ptr将在其作用域结束时自动释放内存。不需要显式调用delete来释放内存,scoped_ptr析构函数会自动处理。
{
std::scoped_ptr<int> ptr(new int(42));
// 在作用域结束时自动释放内存
} // 内存被自动释放
上述代码示例中,在 scoped_ptr 的作用域结束时,指向的对象会自动被释放,无需手动调用 delete。
需要注意的是,scoped_ptr 是 C++11 之前的标准库提供的智能指针,从 C++11 开始已经被废弃,推荐使用 std::unique_ptr 代替。std::unique_ptr 提供了更丰富的功能和更好的语义。在新的代码中,建议使用 std::unique_ptr 或其他智能指针类来管理动态分配的对象。
希望这些解释对你有所帮助!
unique_ptr
unique_ptr需要手动初始化,声明的时候必须用模板参数指定类型:
unique_ptr<int> ptr1(new int(10)); // int智能指针
assert(*ptr1 = 10); // 可以使用*取内容
assert(ptr1 != nullptr); // 可以判断是否为空指针
unique_ptr<string> ptr2(new string("hello")); // string智能指针
assert(*ptr2 == "hello"); // 可以使用*取内容
assert(ptr2->size() == 5); // 可以使用->调用成员函数
也可以调用工厂函数,强制创建智能指针的时候必须初始化:
auto ptr3 = make_unique<int>(42); // 工厂函数创建智能指针
assert(ptr3 && *ptr3 == 42);
auto ptr4 = make_unique<string>("god of war"); // 工厂函数创建智能指针
assert(!ptr4->empty());
unique_ptr表示该智能指针的所有权是唯一的,不允许共享,任何时候只能有一个人持有。
它禁止拷贝赋值,但是可以使用std::move()显式地声明所有权转移:
auto ptr1 = make_unique<int>(42); // 工厂函数创建智能指针
assert(ptr1 && *ptr1 == 42); // 此时智能指针有效
auto ptr2 = ptr1; // 编译有问题
auto ptr2 = std::move(ptr1); // 使用move()转移所有权
assert(ptr2 && *ptr2 == 42);
assert(ptr1); // 此时智能指针无效.会报错
指针的所有权就被转走了,原来的 unique_ptr 变成了空指针,新的 unique_ptr 接替了管理权,保证所有权的唯一性
shared_ptr
基本使用方式与unique_ptr并无不同:
shared_ptr<int> ptr1(new int(10)); // int智能指针
assert(*ptr1 = 10); // 可以使用*取内容
shared_ptr<string> ptr2(new string("hello")); // string智能指针
assert(*ptr2 == "hello"); // 可以使用*取内容
auto ptr3 = make_shared<int>(42); // 工厂函数创建智能指针
assert(ptr3 && *ptr3 == 42); // 可以判断是否为空指针
auto ptr4 = make_shared<string>("zelda"); // 工厂函数创建智能指针
assert(!ptr4->empty()); // 可以使用->调用成员函数
不过它的所有权可以被安全共享,支持拷贝赋值
auto ptr1 = make_shared<int>(42); // 工厂函数创建智能指针
assert(ptr1 && ptr1.unique() ); // 此时智能指针有效且唯一
auto ptr2 = ptr1; // 直接拷贝赋值,不需要使用move()
assert(ptr1 && ptr2); // 此时两个智能指针均有效
assert(ptr1 == ptr2); // shared_ptr可以直接比较
// 两个智能指针均不唯一,且引用计数为2
assert(!ptr1.unique() && ptr1.use_count() == 2);
assert(!ptr2.unique() && ptr2.use_count() == 2);
其内部使用引用计数,所以具有完整的”值语义“,可以在任何场合下代替原始指针。
不过维护引用计数的存储和管理都是成本,过度使用会降低运行效率。其引用计数也会带来循环引用,下面是简化后的典型例子:
#include <iostream>
#include <memory>
#include <assert.h>
using namespace std;
class Node final {
public:
using this_type = Node;
using shared_type = std::shared_ptr<this_type>;
shared_type next; // 使用只能指针来指向下一个节点
};
int main() {
auto n1 = make_shared<Node>(); // 工厂函数创建智能指针
auto n2 = make_shared<Node>();
// 此时引用计数均为1
assert(n1.use_count() == 1);
assert(n2.use_count() == 1);
// 产生循环引用
n1->next = n2;
n2->next = n1;
// 此时引用计数均为2,且无法减到0,内存泄露
assert(n1.use_count() == 2);
assert(n2.use_count() == 2);
}
这个例子很简单,你一下子就能看出存在循环引用。但在实际开发中,指针的关系可不像例 子那么清晰,很有可能会不知不觉形成一个链条很长的循环引用,复杂到你根本无法识别, 想要找出来基本上是不可能的。 想要从根本上杜绝循环引用,光靠 shared_ptr 是不行了,必须要用到weak_ptr。
weak_ptr
它专门为打破循环引用而设计,只观察指针,不会增 加引用计数(弱引用),但在需要的时候,可以调用成员函数 lock(),获取 shared_ptr(强引用) 。用法如下
#include <iostream>
#include <memory>
#include <assert.h>
using namespace std;
class Node final {
public:
using this_type = Node;
// 改用weak_ptr
using shared_type = std::weak_ptr<this_type>;
shared_type next; // 使用只能指针来指向下一个节点
};
int main() {
auto n1 = make_shared<Node>(); // 工厂函数创建智能指针
auto n2 = make_shared<Node>();
// 此时引用计数均为1
assert(n1.use_count() == 1);
assert(n2.use_count() == 1);
// 产生循环引用
n1->next = n2;
n2->next = n1;
// 因为使用了weak_ptr,引用计数为1
assert(n1.use_count() == 1);
assert(n2.use_count() == 1);
if (!n1->next.expired()) { // 检查指针是否有效
auto ptr = n1->next.lock(); // lock()获取shared_ptr
assert(ptr == n2);
}
}
使用建议
在上面的代码中需要注意,我们使用std::make_shared代替new的方式创建shared_ptr。
2、在类内部调用其他类的方法
不可以在A内部定义B、C的shared_ptr,而是应该使用weak_ptr
class A {
private:
std::weak_ptr<B> b_;
std::weak_ptr<C> c_;
public:
void do_something();
void set_b_c(const std::shared_ptr<B>& b, const std::shared_ptr<C>& c) {
b_ = b;
c_ = c;
}
};
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class A;
class D {
private:
std::shared_ptr<A> a_;
public:
D(std::shared_ptr<A>& a): a_(a) {}
};
class A : public std::enable_shared_from_this<A>{
public:
void new_D()
{
// 错误方式,用this指针重新构造shared_ptr,将导致二次释放当前对象,重新创建的shared_ptr与当前对象的shared_ptr没有关系,没有增加当前对象的引用计数
// 关于这个问题,可以详细地看一下这篇博客:https://www.cnblogs.com/jo3yzhu/p/11358400.html
// std::shared_ptr<A> this_shared_ptr1(this);
// std::unique_ptr<D> d1(new D(this_shared_ptr1));
// 正确方式 对象继承了enable_shared_from_this后,可以通过shared_from_this()获取当前对象的shared_ptr指针。
std::shared_ptr<A> this_shared_ptr2 = shared_from_this();
std::unique_ptr<D> d2(new D(this_shared_ptr2));
}
};
int main() {
std::shared_ptr<A> a(new A());
a->new_D();
}
4、智能指针只能管理堆对象,不能管理栈上对象
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class A;
class D {
private:
std::shared_ptr<A> a_;
public:
D(std::shared_ptr<A>& a): a_(a) {}
};
class A : public std::enable_shared_from_this<A>{
public:
void new_D()
{
// 错误方式,用this指针重新构造shared_ptr,将导致二次释放当前对象,重新创建的shared_ptr与当前对象的shared_ptr没有关系,没有增加当前对象的引用计数
// 关于这个问题,可以详细地看一下这篇博客:https://www.cnblogs.com/jo3yzhu/p/11358400.html
// std::shared_ptr<A> this_shared_ptr1(this);
// std::unique_ptr<D> d1(new D(this_shared_ptr1));
// 正确方式 对象继承了enable_shared_from_this后,可以通过shared_from_this()获取当前对象的shared_ptr指针。
std::shared_ptr<A> this_shared_ptr2 = shared_from_this();
std::unique_ptr<D> d2(new D(this_shared_ptr2));
}
};
int main() {
std::shared_ptr<A> a(new A());
a->new_D();
}
栈上对象本身在出栈的时候就会被自动销毁,如果将其指针交给智能指针,会造成对象的二次销毁。
5、不能将this指针直接托管给智能指针
如果对一个类初始化为智能指针,那么在类内部函数用this指针重新构造shared_ptr,不会增加引用计数,会导致当前对象的二次释放。
class A { public: void function() { // 错误方式 std::shared_ptr<A> this_shared_ptr(this); } }; std::shared_ptr<A> e = std::make_shared<E>();shared_ptr是管理共享资源的利器,需要注意避免循环引用,通常的做法是owner持有指向child的shared_ptr,child持有指向owner的weak_ptr。
这种做法可以解决循环引用的问题。当 owner 不再需要 child 时,owner 的
shared_ptr被释放,同时减少 child 的引用计数。如果没有其他shared_ptr持有 child,则 child 也会被销毁。7、访问原始指针采用*而非.get()
request.get(): 这是boost::shared_ptr类的成员函数,用于获取指向的原始裸指针。使用get()函数可以获得指向被shared_ptr管理的对象的裸指针,但请注意,当您使用裸指针时,将失去shared_ptr提供的管理功能,这可能导致潜在的内存泄漏或悬空指针问题。因此,除非特定情况下需要裸指针,否则最好避免使用get()函数。*request: 这是对boost::shared_ptr的解引用操作符。这将返回由shared_ptr所管理的对象的引用。通过解引用操作符,您可以直接访问由shared_ptr管理的对象。这种方式更安全,因为它维持了shared_ptr提供的所有权和引用计数的机制。
因此,总体而言,应当优先选择使用 *request 而不是 request.get(),以便利用 shared_ptr 提供的安全管理功能。
以下是一个简单的示例,演示了 request.get() 和 *request 之间的区别:
#include <iostream>
#include <boost/shared_ptr.hpp>
class MyClass {
public:
void display() {
std::cout << "Hello from MyClass!" << std::endl;
}
};
int main() {
// 创建一个 MyClass 对象的 shared_ptr
boost::shared_ptr<MyClass> ptr(new MyClass());
// 使用 shared_ptr 的构造函数创建另一个 shared_ptr
boost::shared_ptr<MyClass> anotherPtr(ptr);
// 使用 request.get() 获取裸指针
MyClass* rawPtr = ptr.get();
// 使用 *request 解引用
(*ptr).display();
anotherPtr->display();
// 修改原始指针会影响到共享的智能指针
rawPtr->display();
// 此时删除原始指针,但不影响 anotherPtr
delete rawPtr;
// 通过智能指针访问已经被删除的内存,可能导致未定义行为
// anotherPtr->display(); // 这里将导致未定义行为
return 0;
}
在这个示例中,我们创建了一个 MyClass 类的 shared_ptr 对象。然后使用 get() 函数获得了指向原始对象的裸指针,并且通过解引用操作符 *ptr 和箭头操作符 -> 来访问该对象的成员函数。随后删除了这个裸指针,但由于还有另一个 shared_ptr 持有相同的对象,这可能导致未定义行为。这个示例展示了在使用裸指针时要格外小心,而智能指针则提供了更安全的内存管理方式。
No comments yet.