a review at smart pointer(4)

来源 https://www.cnblogs.com/jiayayao/p/6128877.html

#include "stdafx.h"
#include <iostream>
#include <future>
#include <thread>

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
    p1.reset();//Person(3)的引用计数为1
    p3.reset();//Person(3)的引用计数为0,析构Person(3)
    return 0;
}
  • 首先我们解释一下什么是引用计数,首先初始化一个shared ptr p1,然后其中的成员是new Person(1)
  • 内存布局可以理解为,一个heap区域的person类其地址为(假设为hp1),然后一个shared ptr在stack区,叫做p1
  • 然后同样的:heap区域的person类person(2)和其地址为hp2,然后一个shared ptr在stack区:p2
  • p1 reset一个new person(3), 这个的解释过程为:首先在heap区new一个person 3,其地址为hp3,然后,执行reset函数,因为reset的目的永远是释放一个count,所以count - 1变成0,然后析构person(1),所以,hp1消失,然后hp3交给p1,count自增
  • 然后复制一个shared ptr p3,值是p1,里面的内容也是一样的, 我们发现hp3既交给了p1也交给了p3,count为2
  • p1.reset, count--
  • p3.reset, count--

所以我们可以发现,share ptr的share为什么是share? 它代表的是多个share指针共享一个堆资源

之后我有个硕大的疑问

  • 来源某pp prime的一句话:shared_ptr自动销毁所管理的对象
    当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象,它是通过另一个特殊的成员函数-析构函数完成销毁工作的,类似于构造函数,每个类都有一个析构函数。析构函数控制对象销毁时做什么操作。析构函数一般用来释放对象所分配的资源。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。
  • 然后不仅仅是这一个博客上有说:不要用一个原始指针初始化多个shared_ptr,原因在于,会造成二次销毁
    int *p5 = new int;
    std::shared_ptr<int> p6(p5);
    std::shared_ptr<int> p7(p5);// logic error
  • 上述代码很好理解,二次销毁谁不知道

我的问题是,这篇文章的第一份代码不也是两个指针指向一个资源吗?为什么这个就没有错呢?

答案是:第一份代码是两个智能指针有着一模一样的值,它是shared ptr(语义上),指向一个资源,它们在stack区域的值是一模一样的,=之间应该是有运算符重载的机制让count自增(代表了shared,所以count自增)

但是第二个例子不对的地方在于,它并不是智能指针的赋值,它是让一个指针进入两个构造函数,所以count应该是无自增的机制的

第一个例子由shared ptr自己的机制进行垃圾回收,根据count何时减到0

  • 另外,不要在函数实参中创建shared_ptr
// 错
function(shared_ptr<int>(new int), g());

通过查阅文档我们发现unique ptr的默认删除器是支持释放数组对象的,如: std::unique_ptr<int[]> foo (new int[5]); 但是shared ptr 不支持[] ,所以我们要自定义deleter

关于到底是int[]还是int,自定义(不自定义)deleter的问题,我直说了: 我看不懂,因为20,17,11三个版本的要求全不一样,感兴趣的参见这篇博客 https://www.cnblogs.com/apocelipes/p/10346928.html 到时候开发就现查文档

  • 另外,注意unique ptr可以转shared ptr,反过来不可,比如
#include <iostream>
#include <memory>
using namespace std;

class A{
public:
    string id;
    A(string id):id(id){cout<<id<<":构造函数"<<endl;}
    ~A(){cout<<id<<":析构函数"<<endl;}
};

int main() {
    unique_ptr<A> a(new A("unique_ptr"));
    shared_ptr<A> b = move(a);
//    a = move(b);  // 报错
//    a.reset(b.get());  // 运行错误
    cout<<a.get()<<endl;
    return 0;
}
  • 用get拿到智能指针的裸指针之后删掉它,会导致智能指针运行错误

  • 不要用stack中的变量地址初始化一个smart pointer

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

class A{
public:
    string id;
    A(string id):id(id){cout<<id<<":构造函数"<<endl;}
    ~A(){cout<<id<<":析构函数"<<endl;}
};

A a("全局变量");

int main() {
    A b("局部变量");
//    unique_ptr<A> pa(&a); // 运行错误
    unique_ptr<A> pa(&b);
    return 0;
}

谨慎使用智能指针的get与release方法

  • 通过unique_ptr.release()方法返回的裸指针,需要我们自己delete删除对象,因为调用release方法后,该unique_ptr不再拥有对象的所有权。

循环引用问题待更

继续详细的过一遍shared ptr的文档,这些是成员函数

构造函数

// shared_ptr constructor example
#include <iostream>
#include <memory>

struct C {int* data;};

int main () {
  std::shared_ptr<int> p1;
  std::shared_ptr<int> p2 (nullptr);
  std::shared_ptr<int> p3 (new int);
  std::shared_ptr<int> p4 (new int, std::default_delete<int>());
  std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());
  std::shared_ptr<int> p6 (p5);
  std::shared_ptr<int> p7 (std::move(p6));
  std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
  std::shared_ptr<C> obj (new C);
  std::shared_ptr<int> p9 (obj, obj->data);

  std::cout << "use_count:\n";
  std::cout << "p1: " << p1.use_count() << '\n';
  std::cout << "p2: " << p2.use_count() << '\n';
  std::cout << "p3: " << p3.use_count() << '\n';
  std::cout << "p4: " << p4.use_count() << '\n';
  std::cout << "p5: " << p5.use_count() << '\n';
  std::cout << "p6: " << p6.use_count() << '\n';
  std::cout << "p7: " << p7.use_count() << '\n';
  std::cout << "p8: " << p8.use_count() << '\n';
  std::cout << "p9: " << p9.use_count() << '\n';
  return 0;
}

输出结果:

use_count:
p1: 0
p2: 0
p3: 1
p4: 1
p5: 2
p6: 0
p7: 2
p8: 1
p9: 2
  • p1的ref count是0,p2的ref count是0,p3是1,p4是1,p5是1,p6是2,p5是2,p6因为move了变成0,p7是2, p8是1, p9涉及Aliasing constructor,看不懂,是2

析构函数

// shared_ptr destructor example
#include <iostream>
#include <memory>

int main () {
  auto deleter = [](int*p){
    std::cout << "[deleter called]\n"; delete p;
  };

  std::shared_ptr<int> foo (new int,deleter);

  std::cout << "use_count: " << foo.use_count() << '\n';

  return 0;                        // [deleter called]
}
  • 输出
use_count: 1
[deleter_called]
  • 我们可以看到,它自定义了一个deleter,这个deleter是个lambda表达式,然后<>里是没有那个unique ptr的deleter参数的

operator=

// shared_ptr::operator= example
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> foo;
  std::shared_ptr<int> bar (new int(10));

  foo = bar;                          // copy

  bar = std::make_shared<int> (20);   // move

  std::unique_ptr<int> unique (new int(30));
  foo = std::move(unique);            // move from unique_ptr

  std::cout << "*foo: " << *foo << '\n';
  std::cout << "*bar: " << *bar << '\n';

  return 0;
}
  • 输出
*foo: 30
*bar: 20
  • 不解释了,没啥好解释的

swap

// shared_ptr::swap example
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> foo (new int(10));
  std::shared_ptr<int> bar (new int(20));

  foo.swap(bar);

  std::cout << "*foo: " << *foo << '\n';
  std::cout << "*bar: " << *bar << '\n';

  return 0;
}
  • output
*foo: 20
*bar: 10
  • 没啥解释的

get()

// shared_ptr::get example
#include <iostream>
#include <memory>

int main () {
  int* p = new int (10);
  std::shared_ptr<int> a (p);

  if (a.get()==p)
    std::cout << "a and p point to the same location\n";

  // three ways of accessing the same address:
  std::cout << *a.get() << "\n";
  std::cout << *a << "\n";
  std::cout << *p << "\n";

  return 0;
}
  • output
a and p point to the same location
10
10
10
  • 我们可以发现,不论是智能指针还是raw pointer,都是一个值,估计是智能指针对 * 做了overloading
// shared_ptr::operator*
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> foo (new int);
  std::shared_ptr<int> bar (new int (100));

  *foo = *bar * 2;

  std::cout << "foo: " << *foo << '\n';
  std::cout << "bar: " << *bar << '\n';

  return 0;
}
  • output
foo: 200
bar: 100
  • 不解释了

operator ->

// shared_ptr::operator->
#include <iostream>
#include <memory>

struct C { int a; int b; };

int main () {
  std::shared_ptr<C> foo;
  std::shared_ptr<C> bar (new C);

  foo = bar;

  foo->a = 10;
  bar->b = 20;

  if (foo) std::cout << "foo: " << foo->a << ' ' << foo->b << '\n';
  if (bar) std::cout << "bar: " << bar->a << ' ' << bar->b << '\n';

  return 0;
}
  • output
foo: 10 20
bar: 10 20

use_count()

  • Returns the number of shared_ptr objects that share ownership over the same pointer as this object (including it).

  • If this is an empty shared_ptr, the function returns zero.

  • Library implementations are not required to keep a count of any particular set of owners, and thus it may not be efficient to call this function. To check specifically whether use_count is 1, you can use member unique instead, which may be faster. 让你用unique检测有么有至少一个引用

unique()

  • 这是个bool的返回值
// shared_ptr::unique
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> foo;
  std::shared_ptr<int> bar (new int);

  std::cout << "foo unique?\n" << std::boolalpha;

  std::cout << "1: " << foo.unique() << '\n';  // false (empty)

  foo = bar;
  std::cout << "2: " << foo.unique() << '\n';  // false (shared with bar)

  bar = nullptr;
  std::cout << "3: " << foo.unique() << '\n';  // true

  return 0;
}
  • output
foo unique?
1: false
2: false
3: true

operator bool

// example of shared_ptr::operator bool
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> foo;
  std::shared_ptr<int> bar (new int(34));

  if (foo) std::cout << "foo points to " << *foo << '\n';
  else std::cout << "foo is null\n";

  if (bar) std::cout << "bar points to " << *bar << '\n';
  else std::cout << "bar is null\n";

  return 0;
}
  • output
foo is null
bar points to 34

std::shared_ptr::owner_before

这个涉及aliase constructor,我不会,待更

下篇就是循环引用问题和weak ptr了,马上就结束了。这篇是我最不认真的一篇

人生总是这样,看起来最简单的部分,往往是最需要口舌和脑筋的部分,比如类型转换、操作符重载,看起来就是1+1,实际上很烧脑。反倒是后面这英文文档不说,一大段的成员函数看起来像天书一样,反倒是最不必费口舌的(真的简单的像1+1)资料的收集是真的费时间,也很重要,只不过没谁很在乎你资料怎么收集的,大家都只在乎result,我就是为了写项目,看别人的代码,发现有smart pointer看不懂,结果一扒就纠缠出了从operator overloading开始的一些列话题(毕竟,操作符重载都看不懂,智能指针的文档怎么你能看懂呢?),详细到了赋值和复制。假设你看起来这个文笔很幼稚,那就当它很幼稚吧

posted @ 2021-01-09 18:31  五个桔核  阅读(84)  评论(0)    收藏  举报