c++智能指针
c++智能指针
(因为这一部分的内容对我来说很容易忘,特此整理)
智能指针可以在程序结束时自动释放,这一特性就像调用了构造函数和析构函数
unique_ptr
和普通的指针区别在它会在函数结束时自动释放自己的生命,而普通指针需要程序员记住delete
它,忘记delete
就会造成内存泄露问题
只需要:
std::unique_ptr<int> one = std::make_unique<int>();
这里的make_unique<T>()
相当于调用了创建unique指针的函数(所以不能用{}
)
unique指针不能作为参数传进其他函数,因为它删除了拷贝构造函数:禁止拷贝(如果不删,就可能会出现两次释放同一指针的问题)
要是想传递它,下面有几个解决方案:
-
get下指针,只是暂时借用了指针的数据结构,不掌握指针的生命
class test{ public: test(){ std::cout << "这里是一个unique指针!" << std::endl; } ~test(){ std::cout << "I\'m die——" << std::endl; } void showSelf(){ std::cout << "我被借用一下~" << std::endl; } }; void show(test *p){ p->showSelf(); } int main(){ std::unique_ptr<test> p = std::make_unique<test>(); show(p.get()); return 0; }
-
移动指针的生命到另一个函数里,原函数中的指针被设置为空
vector<unique_ptr> ptrlist{}; // 设置一个全局变量 void func(std::unique_ptr<test> p){ ptrlist.push_back(std::move(p)); // 把p进一步放到ptrlist里 } int main(){ std::unique_ptr<test> p = std::make_unique<test>(); return 0; }
其中的
std::move()
只是一个声明,可以意味类型转化:unique指针需要接受的是右值引用,没有std::move
也可以转化但是遇上vector这种,所有元素的拷贝都是深拷贝(除了智能指针有例外),会把变量拷贝到一个新的内存中(包括数据、包括结构),性能不高;当智能指针指向一个没有拷贝构造函数的类(
Test::test(const test& one)=delete;
)时,拷贝就为浅拷贝(仅拷贝指针)
移动的好处是浅拷贝,不需要再次拷贝结构,速度更快;但我们还想使用移动前的指针,除了在移动指针前先test another = p.get()
,还可以考虑shared_ptr
注意,
test another = p.get()
还要考虑移动后的指针生命;也就是说get是借用着那个移动指针的生命,在移动指针生命结束时、another
也会寄
shared_ptr
shared指针则支持拷贝,像名字一样、可以多个指针指向同一对象;它内置了一个计数器,通过当前共有几个shared指针存在,判断是否释放。只要有一个指针还指着该对象,对象就不会被释放
void func(std::shared_ptr<test> other);
std::shared_ptr<test> one = std::make_shared<test>(); //初始化引用计数为1
func(one); // 引用计数+1,函数退出引用计数-1
func(one); // 引用计数又+1
可以使用one.use_count()
获取one
指针当前的引用计数
这里one.use_count()
调用了指针本身的方法,也就是.
运算符可以访问自己类中的方法;而one->show()
是调用了one
指向的、对象的方法,也就是指针要用->
访问所指对象的类方法
看起来shared_ptr基本无敌了,但它仍有不足
(1)会出现循环引用
struct example{
std::shared_ptr<example> myparent;
std::shared_ptr<example> mychild;
};
int main(){
auto parent = std::make_shared<example>();
auto child = std::makde_shared<example>();
parent->mychild = child; //parent指着example对象,让该对象的mychild指向child
child->myparent = parent; //child指着example对象,让该对象的myparent指向parent
return 0;
}
我们其实只是想使用example类,并用指针方便调类方法,但是上面的例子就出现了child和parent两个指针相互指着,引用计数不为0,结果它们都无法释放最后内存泄漏(可以用不影响计数的普通指针、weak_ptr来避免)
使用普通指针:
struct test{
std::shared_ptr<test> myparent;
test *mychild; // 使用普通指针
};
parent->mychild = child;
child->myparent = parent.get(); // 获取普通指针
使用weak_ptr:
struct test{
std::shared_ptr<test> myparent;
std::weak_ptr<test> mychild;
};
parent->mychild = child; // 指向弱引用
child->myparent = parent;
(2)shared_ptr需要维护一个atomic的引用计数器,额外管理一块内存空间效率较低;访问实际对象需要二级指针,且deleter使用了类型擦除技术
weak_ptr
弱引用weak_ptr
不影响shared_ptr
的引用计数
- 如果有需要可以随时使用
p.lock()
建立强引用,不用lock()
就不影响计数 - shared_ptr失效时(计数为0),weak_ptr的
lock()
将返回nullptr
std::shared_ptr<test> p = std::make_shared<test>();
std::weak_ptr<test> weakp = p;
weakp.lock()->show(); //建立强引用
在类中的智能指针应用
- unique:某一对象仅属于我,比如父子窗口,父窗口指向子窗口时(浏览器关掉一个标签页)
- 原始指针:当某一对象不仅仅属于我,但他被释放前我必然需要先被释放,比如子窗口指向父窗口时(浏览器关闭,诸多标签页需要先一步释放)
- shared:“我”被多个对象共享 || 某对象仅属于“我”,但需要weak_ptr
- weak:该对象不属于我,且他被释放时我可能还需要活着,指向窗口中上一次点击的链接(比如浏览器里,从一个标签页中打开了另一个标签页)