C++指针指针----share_ptr和weak_ptr

来源:微信公众号「编程学习基地」

智能指针简介

为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer)。在现代 c + + 编程中,标准库包含 智能指针,这些指针用于帮助确保程序不会出现内存和资源泄漏,并具有异常安全。C++11提供了三种智能指针:std::shared_ptr, std::unique_ptr, std::weak_ptr,使用时需添加头文件#include<memory>

shared_ptr

shared_ptr 使用引用计数,每一个 shared_ptr 的拷贝都指向相同的内存。每引用它一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。shared_ptr内部的引用计数是安全的,但是对象的读取需要加锁。

智能指针对比普通指针
#include <iostream>
#include <memory>
using namespace std;
class Data
{
public:
    Data() {
        cout << "构造" << endl;
    }
    ~Data() {
        cout << "析构" << endl;
    }
};

int main()
{
    cout<<"智能指针:"<<endl;
    {
        std::shared_ptr<Data> Ptr(new Data);    //智能指针出了作用域就会被释放,引用计数减一
    }
    cout<<"普通指针:"<<endl;
    Data* ptr = new Data;
    delete ptr;
    return 0;
}
ubuntu@VM-16-5-ubuntu:~$ g++ -std=c++11 -o test test.cpp 
ubuntu@VM-16-5-ubuntu:~$ ./test 
智能指针
构造
析构
普通指针
构造
析构
基本用法
#include <iostream>
#include <memory>

using namespace std;
class Person
{
public:
    Person(int v) {
        value = v;
        std::cout << "Cons" <<value<< std::endl;
    }
    ~Person() {
        std::cout << "Des" <<value<< std::endl;
    }
    int value;
};

int main()
{
    std::shared_ptr<Person> p1(new Person(1));	// Person(1)的引用计数为1

    std::shared_ptr<Person> p2 = std::make_shared<Person>(2);

    p1.reset(new Person(3));// 首先生成新对象,然后引用计数减1,引用计数为0,故析构Person(1)
    // 最后将新对象的指针交给智能指针
    std::shared_ptr<Person> p3 = p1;//现在p1和p3同时指向Person(3),Person(3)的引用计数为2
	std::cout<<p3.use_count()<<std::endl;
	std::cout<<p1.use_count()<<std::endl;

    p1.reset();//Person(3)的引用计数为1
    //p3.reset();//Person(3)的引用计数为0,析构Person(3)
    std::cout<<"over"<<std::endl;
    return 0;
}
Cons1
Cons2
Cons3
Des1
2
2
over
Des3
Des2
其他用法
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
    int i;
    A(int n):i(n) { };
    ~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
    //错误使用
    //A* p = new A(10);
	//shared_ptr <A> sp1(p), sp2(p);
    
    shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
    shared_ptr<A> sp2(sp1);       //A(2)同时交由sp2托管
    shared_ptr<A> sp3;
    sp3 = sp2;   //A(2)同时交由sp3托管
    cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
    A * p = sp3.get();      // get返回托管的指针,p 指向 A(2)
    cout << p->i << endl;  //输出 2
    sp1.reset(new A(3));    // reset导致托管新的指针, 此时sp1托管A(3)
    sp2.reset(new A(4));    // sp2托管A(4)
    cout << sp1->i << endl; //输出 3
    sp3.reset(new A(5));    // sp3托管A(5),A(2)无人托管,被delete
    cout << "end" << endl;
    return 0;
}
2,2,2
2
3
2 destructed
end
5 destructed
4 destructed
3 destructed

reset()包含两个操作。当智能指针中有值的时候,调用reset()会使引用计数减1.当调用reset(new xxx())重新赋值时,智能指针首先是生成新对象,然后将就对象的引用计数减1(当然,如果发现引用计数为0时,则析构旧对象),然后将新对象的指针交给智能指针保管。

智能指针引用计数为0,释放的对象是
#include <vector>
#include <memory>
#include <iostream>
using namespace std;
class Father
{
public:
	Father(std::string name) {}
	~Father() {cout<<"父类析构"<<endl;}
	std::string m_name;
};

typedef std::shared_ptr< Father > FatherPtr;

class Son:public Father
{
public:
	Son(std::string name) :Father(name) { m_name = name; }
	~Son() { cout<<"子类析构"<<endl; }
};

typedef std::shared_ptr<Father> FatherPtr;
int main()
{
    {
        FatherPtr test(new Son("deroy"));
    }
    cout<<"-------------------------"<<endl;
    Father* Ptr = new Son("test");
    delete Ptr;
    return 0;
}
ubuntu@VM-16-5-ubuntu:~$ g++ -std=c++11 -o test test.cpp 
ubuntu@VM-16-5-ubuntu:~$ ./test 
子类析构
父类析构
-------------------------
父类析构
注意事项
  1. 智能指针管理的是堆上面的指针,(栈上面的地址会造成两次调用析构)
  2. shared_ptr相当于一个指针,拷贝和赋值会是的引用加一
std::shared_ptr<Person> p1(new Person(1));	// Person(1)的引用计数为1
std::shared_ptr<Person> p3 = p1;//现在p1和p3同时指向Person(1),Person(3)的引用计数为2
std::cout<<p3.use_count()<<std::endl;	//引用计数为2
std::cout<<p1.use_count()<<std::endl;	//引用计数为2
  1. 只有当引用计数为0时,才会释放内存
/*接上面的代码*/
p1.reset();	//Person(1)的引用计数为1
//p3.reset();//Person(1)的引用计数为0,析构Person(3)
//只重置p1,不重置p3,内存不会释放
  1. 不要用一个原始指针初始化多个shared_ptr,原因在于,会造成二次销毁,如下所示:
#include <iostream>
#include <memory>
using namespace std;
class Person
{
public:
    Person(int v) {
        value = v;
        std::cout << "Cons" <<value<< std::endl;
    }
    ~Person() {
        std::cout << "Des" <<value<< std::endl;
    }
    int value;
};

int main()
{
    Person *p5 = new Person(5);
    std::shared_ptr<Person> p6(p5);
    std::shared_ptr<Person> p7(p5);// logic error
    //std::shared_ptr<Person> p7(p6);	// 这行是正确的
    return 0;
}
Cons5
Des5
Des0
  1. 避免循环引用
完整代码
#include <iostream>
#include <memory>

using namespace std;
class Person
{
public:
    Person(int v) {
        value = v;
        std::cout << "Cons" <<value<< std::endl;
    }
    ~Person() {
        std::cout << "Des" <<value<< std::endl;
    }
    int value;
};

int main()
{
    std::shared_ptr<Person> p1(new Person(1));	// Person(1)的引用计数为1

    std::shared_ptr<Person> p2 = std::make_shared<Person>(2);

    p1.reset(new Person(3));// 首先生成新对象,然后引用计数减1,引用计数为0,故析构Person(1)
    // 最后将新对象的指针交给智能指针

    std::shared_ptr<Person> p3 = p1;//现在p1和p3同时指向Person(3),Person(3)的引用计数为2
	std::cout<<p3.use_count()<<std::endl;
	std::cout<<p1.use_count()<<std::endl;

    p1.reset();//Person(3)的引用计数为1
    //p3.reset();//Person(3)的引用计数为0,析构Person(3)
    std::cout<<"over"<<std::endl;
    return 0;
}
Cons1
Cons2
Cons3
Des1
2
2
over
Des3
Des2
智能指针相关的函数总结
成员函数作用
reset()重置智能指针,delete其关联的指针。
release()不delete关联指针,并返回关联指针。 释放关联指针的所有权,智能指针为空。
get()仅仅返回关联指针
use_count()获取引用计数
std作用
std::make_unique创建 unique_ptr 对象C++14
std::make_shared创建 make_shared 对象C++14
std::move()对象转移
weak_ptr作用
expired()判断智能指针是否失效
lock()获取其对应的shared_ptr对象

weak_ptr

shared_ptrweak_ptr用来解决环形引用的问题

weak_ptr本身也是一个模板类,但是不能直接用它来定义一个智能指针的对象,只能配合shared_ptr来使用,可以将shared_ptr的对象赋值给weak_ptr,并且这样并不会改变引用计数的值。查看weak_ptr的代码时发现,它主要有lockswapresetexpiredoperator=use_count几个函数,与shared_ptr相比多了lockexpired函数,但是却少了get函数,甚至连operator*operator->都没有

其实weak_ptr本身设计的很简单,就是为了辅助shared_ptr的,它本身不能直接定义指向原始指针的对象,只能指向shared_ptr对象,同时也不能将weak_ptr对象直接赋值给shared_ptr类型的变量,最重要的一点是赋值给它不会增加引用计数:

基本用法
#include <memory>
#include <iostream>

class foo
{
public:
    foo()
    {
        std::cout << "foo construct.." << std::endl;
    }

    void method()
    {
        std::cout << "welcome Test foo.." << std::endl;
    }

    ~foo()
    {
        std::cout << "foo destruct.." << std::endl;
    }
};

int main()
{
    // weak_ptr
    foo* foo2 = new foo();

    // share_ptr 管理对象
    std::shared_ptr<foo> shptr_foo2(foo2);

    // weak_ptr 弱引用
    std::weak_ptr<foo> weak_foo2(shptr_foo2);

    //由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下
    if (!weak_foo2.expired())
	{
        // 如果要获取数据指针,需要通过lock接口获取
		weak_foo2.lock()->method();
	}

    std::shared_ptr<foo> tmp =  weak_foo2.lock();
    // 这个时候有两个共享指针共享数据,所以use_count数量为2
    std::cout<< "share_ptr use_count:"<<shptr_foo2.use_count()<<std::endl;
    // 我们这边有尝试多次获取所有权(lock),看一下引用计数个数
    // 在获取所有权的时候又构建了一个共享指针,所以此时 use_count 数量为3
    std::cout << "shptr_foo2 RefCount: " << weak_foo2.lock().use_count() << std::endl;
    return 0;
}
# ./main
foo construct..
welcome Test foo..
share_ptr use_count:2
shptr_foo2 RefCount: 3
foo destruct..

来自:https://www.cnblogs.com/blog-yejy/archive/2018/09/30/9727070.html

常用函数用法

weak_ptr中只有函数lockexpired两个函数比较重要,因为它本身不会增加引用计数,所以它指向的对象可能在它用的时候已经被释放了,所以在用之前需要使用expired函数来检测是否过期,然后使用lock函数来获取其对应的shared_ptr对象,然后进行后续操作:

环形引用问题
#include <iostream>
#include <memory>

class Child;
class Parent;

class Parent {
private:
    //std::shared_ptr<Child> ChildPtr;
    std::weak_ptr<Child> ChildPtr;
public:
    void setChild(std::shared_ptr<Child> child) {
        this->ChildPtr = child;
    }

    void doSomething() {
        //new shared_ptr
        if (this->ChildPtr.lock()) {
        }
    }
    ~Parent() {
    }
};

class Child {
private:
    std::shared_ptr<Parent> ParentPtr;
public:
    void setPartent(std::shared_ptr<Parent> parent) {
        this->ParentPtr = parent;
    }
    void doSomething() {
        if (this->ParentPtr.use_count()) {

        }
    }
    ~Child() {
    }
};

int main() {
    std::weak_ptr<Parent> wpp;
    std::weak_ptr<Child> wpc;
    {
        std::shared_ptr<Parent> p(new Parent);
        std::shared_ptr<Child> c(new Child);
        p->setChild(c);
        c->setPartent(p);
        wpp = p;
        wpc = c;
        std::cout << p.use_count() << std::endl; // 2
        std::cout << c.use_count() << std::endl; // 1
    	std::cout << wpp.use_count() << std::endl;  // 2
    	std::cout << wpc.use_count() << std::endl;  // 1
    }
    std::cout << wpp.use_count() << std::endl;  // 0
    std::cout << wpc.use_count() << std::endl;  // 0
    return 0;
}
2
1
2
1
0
0

shared_ptr和weak_ptr详细示例

#include <iostream>
#include <memory>
using namespace std;

class CB;
class CA
{
public:
	CA() { cout << "CA() called! " << endl; }
	~CA() { cout << "~CA() called! " << endl; }
	void set_ptr(shared_ptr<CB>& ptr) { m_ptr_b = ptr; }
	void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
	void show() { cout << "this is class CA!" << endl; }
private:
	shared_ptr<CB> m_ptr_b;
};

class CB
{
public:
	CB() { cout << "CB() called! " << endl; }
	~CB() { cout << "~CB() called! " << endl; }
	void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
	void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
	void show() { cout << "this is class CB!" << endl; }
private:
	weak_ptr<CA> m_ptr_a;
};

void test_refer_to_each_other()
{
	shared_ptr<CA> ptr_a(new CA());
	shared_ptr<CB> ptr_b(new CB());

	cout << "a use count : " << ptr_a.use_count() << endl;
	cout << "b use count : " << ptr_b.use_count() << endl;

	ptr_a->set_ptr(ptr_b);
	ptr_b->set_ptr(ptr_a);


	cout << "a use count : " << ptr_a.use_count() << endl;
	cout << "b use count : " << ptr_b.use_count() << endl;
}

// 测试weak_ptr用法
void test1()
{
	// 编译错误 // error C2665: “std::weak_ptr<CA>::weak_ptr”: 3 个重载中没有一个可以转换所有参数类型
	// weak_ptr<CA> ptr_1(new CA());

	shared_ptr<CA> ptr_1(new CA());

	cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 1

	shared_ptr<CA> ptr_2 = ptr_1;

	cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
	cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2

	weak_ptr<CA> wk_ptr = ptr_1;

	cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
	cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2

	// 编译错误
	// error C2440 : “初始化”: 无法从“std::weak_ptr<CA>”转换为“std::shared_ptr<CA>”
	// shared_ptr<CA> ptr_3 = wk_ptr;
}

// 
// 测试weak_ptr常用函数用法
void test2()
{
	shared_ptr<CA> ptr_a(new CA());		// 输出:CA() called!
	shared_ptr<CB> ptr_b(new CB());		// 输出:CB() called!

	cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
	cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1

	weak_ptr<CA> wk_ptr_a = ptr_a;
	weak_ptr<CB> wk_ptr_b = ptr_b;

	if (!wk_ptr_a.expired())
	{
		wk_ptr_a.lock()->show();		// 输出:this is class CA!
	}

	if (!wk_ptr_b.expired())
	{
		wk_ptr_b.lock()->show();		// 输出:this is class CB!
	}

	// 编译错误
	// 编译必须作用于相同的指针类型之间
	// wk_ptr_a.swap(wk_ptr_b);			// 调用交换函数

	wk_ptr_b.reset();					// 将wk_ptr_b的指向清空
	if (wk_ptr_b.expired())
	{
		cout << "wk_ptr_b is invalid" << endl;	// 输出:wk_ptr_b is invalid 说明改指针已经无效
	}

	wk_ptr_b = ptr_b;
	if (!wk_ptr_b.expired())
	{
		wk_ptr_b.lock()->show();		// 输出:this is class CB! 调用赋值操作后,wk_ptr_b恢复有效
	}

	// 编译错误
	// 编译必须作用于相同的指针类型之间
	// wk_ptr_b = wk_ptr_a;


	// 最后输出的引用计数还是1,说明之前使用weak_ptr类型赋值,不会影响引用计数
	cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
	cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
}

int main(int argc, char* argv[])
{

	test_refer_to_each_other();
	test1();
	test2();
	getchar();
	return 0;
}
a use count : 1
b use count : 1
a use count : 1
b use count : 2
~CA() called!
~CB() called!
CA() called!
ptr_1 use count : 1
ptr_1 use count : 2
ptr_2 use count : 2
ptr_1 use count : 2
ptr_2 use count : 2
~CA() called!
CA() called!
CB() called!
ptr_a use count : 1
ptr_b use count : 1
this is class CA!
this is class CB!
wk_ptr_b is invalid
this is class CB!
ptr_a use count : 1
ptr_b use count : 1
~CB() called!
~CA() called!
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeRoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值