一、智能指针有多少种构造方法?
- 默认构造。构造一个空的智能指针;
std::unique_ptr<int> ptr1;
std::shared_ptr<int> ptr1;
- 原始指针构造。
std::unique_ptr<int> ptr2(new int(42));
std::shared_ptr<int> ptr2(new int(42));
- 移动构造。利用另一个已经构造的智能指针进行构造;
std::unique_ptr<int> ptr3(std::move(ptr2));
std::shared_ptr<int> sptr2 = std::move(sptr1);
- 拷贝构造。(声明为
explicit
)
std::unique_ptr<int> uptr1 = std::make_unique<int>(42);
std::shared_ptr<int> sptr1 = std::make_shared<int>(42);
- 带删除器构造函数声明
前面的例子模板参数只有一个,事实上为了增加智能指针的灵活性,我们还可以传递一个删除器。删除器可以是:函数、有operator()的类和Lambda表达式。
#include <iostream>
#include <memory>
void customDeleter(int* ptr) {
std::cout << "Custom deleter (function) is deleting the pointer\n";
delete ptr;
}
int main() {
std::unique_ptr<int, void(*)(int*)> ptr(new int, customDeleter);
*ptr = 42;
std::cout << "Value: " << *ptr << "\n";
return 0;
}
二、为什么拷贝构造要声明为隐式?
explicit关键字是用来修饰构造函数和转换运算符的,表示禁止隐式转换。转换运算符是一种特殊的成员函数,它允许一个类类型的对象被转换为另一种类型。基本格式是:
operator 类型名() const;
查看下面代码:
void func(std::unique_ptr<MyClass> ptr) { /*...*/ }
MyClass* raw_ptr = new MyClass();
func(raw_ptr);
C++通过声明拷贝构造函数为隐式禁止了上述行为,编译阶段就会出错。假设我们允许这种行为,在原始指针raw_ptr
发生隐式转换,std::unique_ptr
将会接管这部分内存,程序运行完成后,因为引用计数变成了0,所以将会释放掉这部分内存。这样一来我们原始指针失效,尝试访问这个原始指针就会出现错误。
三、还有一些别的什么操作吗?
- 赋值操作
sp1=sp2;
up1=up2;//deleted funciton,not ok
- 解引用操作
*sp
*up
- get()方法
p.get()
返回一个与智能指针指向相同的内置指针
reset()
方法
reset有多个重载形式,分别是p.reset()
p.reset(q)
p.reset(q,d)
,作用是将一个智能指针重置为内置空指针(或q
指针)
->mem
操作
指针指针指向对象中的成员
- 指针交换操作
swap(p,q) p.swap(q)
完成指针指向对象的交换
- 隐式转换
定义了智能指针到bool
的隐式转换,sp
和up
在作为条件时都会被隐式转换为bool
型
- 共享指针的特殊成员
p.use_count() p指向对象对应的引用计数,可能很慢,主要用于调试
p.unique() 对象是否只有一个引用计数
五、make_shared是什么?为什么说它更加高效?
std::make_shared
是C++11标准引入的一个用于创建std::shared_ptr
实例的一个实用函数。再讲它为什么高效之前,我们先来看一个例子:
std::shared_ptr<MyClass> ptr(new MyClass(42));
它一共进行了几次内存分配?答:两次。
- new MyClass(42) 使用运算符new在堆上分配了int大小的内存;
- 因为智能指针需要额外信息,所以进行了第二次内存分配;
为了解决这个问题,C++11引入了std::make_shared
:
std::shared_ptr<MyClass> ptr=std::make_shared<MyClass>(42);
make_shared
直接构造了智能指针所需要的内存,只需要一次内存分配。
所谓高效指的构造的时候只需要分配一次内存。
有时候我们也会听到make_shared
更加安全,这又是为什么呢?同样地,我们也来看一个例子:
#include <memory>
class MyClass {
public:
MyClass(int value) {
if (value < 0) {
throw std::runtime_error("Negative value");
}
}
};
int main() {
try {
std::shared_ptr<MyClass> ptr(new MyClass(-1));
}
catch (const std::exception& e) {
// 在这里捕获异常
}
return 0;
}
假设MyClass
抛出一个异常,还没轮到智能指针接管这部分内存,就已经捕获异常了,处理不当可能会造成内存泄漏。如果我们使用std::make_shared
:
int main() {
try {
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>(-1);
}
catch (const std::exception& e) {
// 在这里捕获异常,没有内存泄漏
}
return 0;
}
这样一来,无论构造是否成功都能够成功的析构。