c++学习-智能指针学习

本文记录我对智能指针的学习,主要分为库学习和自己对于智能指针实现部分的学习。
主演参考链接如下:
[从auto_ptr说起]
[C++ 11 创建和使用 unique_ptr]
[shared_ptr线程安全性分析]
[为什么多线程读写shared_ptr要加锁?]
[C++设计模式——代理模式]
[解引用、判空以及比较]
[cplusplus-shared_ptr]

概述

  1. 什么是智能指针?如何理解它?

    智能指针是存储指向动态分配(堆)对象指针的类。简言之,对指向堆对象的指针做了一层封装。注意,指针有好多种,文件指针也是指针。所以,要注意区别,智能指针封装的是指向堆对象的指针。

  2. 为什么要有之智能指针?

    结合上一目对于RAII的学习就很容易理解了,堆空间作用一种系统资源,需要进行管理。在没有智能指针之前,需要程序员自己管理堆空间,可能会导致内存泄露的情形。利用RAII的思路,把资源抽象为类,把对资源的管理封装到类当中,构造时候获取资源,析构的时候释放资源。从而把对资源的管理转化为对对象的管理。那么,指向堆空间的指针,作为句柄,就是我们实际管理的对象。从而有了智能指针类,本质是借助对象,对堆指针所指向的堆空间进行管理。

  3. c++提供了哪些智能指针?

    最新的标准c++11当中提供了unique_ptr, shared_ptr, weak_ptr这三种智能指针。当然,还有之前的auto_ptr

库学习

auto_ptr学习

先开宗明义说清楚,c++11的标准已经废弃了该指针,讲该指针的目的是因为之前学习了RAII,所以知识体系上一脉相承。

  1. auto_ptr的初衷是用来实现智能指针的,实现内存的自动回收。注意它主要是用来实现内存的自动回收。
  2. auto_ptr的基本思想是RAII。把资源抽象为对象,由于内存资源的句柄是指针。所以,先简单把指针看做资源,从而将该指针抽象为一个类,就有了我们的auto_ptr。
  3. 但是,auto_ptr离现在的智能指针还是有区别,因为它没有实现引用计数的技术,并且垃圾回收机制也不是当下主流智能指针使用的。
  4. 当然,好处还是很明显,自动管理资源。

下面讨论下它的bug:

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

class A
{
public:
    A() { cout << "Construct A Object." << endl; }
    ~A() { cout << "Destroy A Object." << endl; }

    void SetA(int value) { m_a = value; }
    int GetA() { return m_a; }

private:
    int m_a;
};

void TestFunc(auto_ptr<A> Obj)
{
    Obj->SetA(20);
    cout << Obj->GetA() << endl;
}

int main()
{
    auto_ptr<A> pAObj(new A());
    //auto_ptr<A> pAObj1 = new A(); **This is wrong expression.

    pAObj->SetA(10);
    cout << pAObj->GetA() << endl;

    TestFunc(pAObj); // 问题出在这!!!

    //cout << pAObj->GetA() << endl; ** This is wrong.

    return 0;
}

我们之前讨论过,用RAII实现的资源管理类是禁止拷贝和赋值的,因为资源是没有拷贝和赋值的语义的。但是,上面的地方在testFunc完成了复制,此时两个指针指向同一块资源,那么testFunc结束之后会自动释放资源。从而,出来访问失败。

下面看下源码:

template <class _Tp> class auto_ptr {
private:
    _Tp* _M_ptr; //实际wrap的指针

public:
    typedef _Tp element_type;

    // 显示构造函数,防止auto_ptr<A> pAObj1 = new A();隐士构造
    explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {}

    // 复制构造函数,知道为什么参数是&吗?
    auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {} 
    template <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW
        : _M_ptr(__a.release()) {}

    // 赋值构造函数
    auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW {
        if (&__a != this) { // 避免自我赋值
            delete _M_ptr;
            _M_ptr = __a.release(); // 释放了右值,保持左值。虽然实现了赋值操作,但是没有实现赋值语义。上面的复制同样没有实习那赋值语义。
        }
        return *this;
    }

    template <class _Tp1>
    auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW {
        if (__a.get() != this->get()) {
            delete _M_ptr;
            _M_ptr = __a.release();
        }
        return *this;
    }

    ~auto_ptr() { delete _M_ptr; }

    // 智能指针一般都要重载"*"和"->"操作符
    _Tp& operator*() const __STL_NOTHROW {
        return *_M_ptr;
    }
    _Tp* operator->() const __STL_NOTHROW {
        return _M_ptr;
    }
    _Tp* get() const __STL_NOTHROW {
        return _M_ptr;
    }
    _Tp* release() __STL_NOTHROW {
        _Tp* __tmp = _M_ptr;
        _M_ptr = 0;
        return __tmp;
    }
    void reset(_Tp* __p = 0) __STL_NOTHROW {
        if (__p != _M_ptr) {
            delete _M_ptr;
            _M_ptr = __p;
        }
    }
};
auto_ptr<int> ptr(p);
auto_ptr<int> ptr2(p);
ptr = ptr2; // ptr2会失效!

auto_ptr的一些缺陷:
1. auto_ptr不能共享所有权,即不要让两个auto_ptr指向同一个对象。资源管理类本生没有复制和赋值语义,但是auto_ptr还是实现了,这样会析构掉右值。
2. auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。
3. auto_ptr只是一种简单的智能指针,没有实现引用计数。如有特殊需求,需要使用其他智能指针,比如share_ptr。
4. auto_ptr不能作为容器对象,STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto_ptr会传递所有权,那么source与sink元素之间就不等价了。

unique_ptr学习

  1. std::unique_ptr实现了独享所有权的语义。这点它和auto_tr是一脉相承的。即不允许两个unique_ptr指向同一个对象,因为第一个对象用new T()返回的指针初始化之后,第二个对象无法拿到之前的指针,想共享只能通过第一个指针的复制。可以它并不支持。
  2. 它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法。语法上就不可行!auto_ptr语法上可行的,但是还是会析构右值。其实没意义,所以unique_ptr改进之后无法复制。
  3. 只能移动unique_ptr。这意味着,内存资源所有权将转移到另一 unique_ptr,并且原始 unique_ptr 不再拥有此资源。

std::unique_ptr实现了独享所有权的语义。一个非空的std::unique_ptr总是拥有它所指向的资源。转移一个std::unique_ptr将会把所有权也从源指针转移给目标指针(源指针被置空)。拷贝一个std::unique_ptr将不被允许,因为如果你拷贝一个std::unique_ptr,那么拷贝结束后,这两个std::unique_ptr都会指向相同的资源,它们都认为自己拥有这块资源(所以都会企图释放)。因此std::unique_ptr是一个仅能移动(move_only)的类型。当指针析构时,它所拥有的资源也被销毁。默认情况下,资源的析构是伴随着调用std::unique_ptr内部的原始指针的delete操作的。

实例1 unique_ptr的使用
#include <memory>

struct Point {
    int x;
    int y;
    Point() : x(0), y(0) { std::cout << "Point() called." << std::endl; }
    Point(int a, int b) : x(a), y(b) { std::cout << "Point(int, int) called." << std::endl; }
    ~Point() { std::cout << "~Point() called." << std::endl; }
};

int main( void ){

    std::unique_ptr<Point> p(new Point(3,4));

    std::cout << p->x << " , " << p->y << std::endl;

    return 0;
}
/*
Point(int, int) called.
3 , 4
~Point() called.
*/
实例2 unique_ptr移动语义

移动语义意味着要转移所有权!

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

struct Point { 
    int a; 
    int b; 
    Point() { a = 0; b = 0; std::cout << "Point() called." << std::endl; }
    Point(int x, int y) { a = x; b = y; std::cout << "Point(int,int) called." << std::endl; }
    ~Point() { std::cout << "~Point() called." << std::endl; }
};

int main () {
  std::unique_ptr<Point> p (new Point(10,20)); // raii
  std::unique_ptr<Point> q; // 没有触发raii,因为没有申请资源,所以也就没有构造。

  p->a = 10;
  p->b = 20;

  q = std::move(p);

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

  return 0;
}
/*
Point(int,int) called.
q: 10 20
~Point() called.
*/
实例3 unique_ptr可以存储在容器中

这点auto_ptr是禁止的。

// unique_ptr::operator->
#include <iostream>
#include <memory>
#include <vector>

struct Point { 
    int a; 
    int b; 
    Point() { a = 0; b = 0; std::cout << "Point() called." << std::endl; }
    Point(int x, int y) { a = x; b = y; std::cout << "Point(int,int) called." << std::endl; }
    ~Point() { std::cout << "~Point() called." << std::endl; }
};

int main () {
  std::unique_ptr<Point> p(new Point(10,20));
  std::unique_ptr<Point> q(new Point(1,2));

  p->a = 1;
  p->b = 2;

  q->a = 3;
  q->b = 4;

  std::vector< std::unique_ptr<Point> > vec;
  vec.push_back( std::move( p ) ); // 必须使用move语义
  vec.push_back( std::move( q ) ); // 必须使用move语义

  int sz = vec.size();
  for( int i = 0; i < sz; ++i ){
    std::cout << vec[i]->a << "," << vec[i]->b << std::endl;
  }

  std::cout << "After vector :" << std::endl;
  if( p ) std::cout << p->a << "," << p->b << std::endl;
  if( q ) std::cout << q->a << "," << q->b << std::endl;

  return 0;
}
/*
Point(int,int) called.
Point(int,int) called.
1,2
3,4
After vector :
~Point() called.
~Point() called.
*/
实例4 unique_ptr可以管理动态数组

由于智能指针就封装了一个指针,所以,存储的还是一个动态数组的首地址指针。用法和int* arr = new int[maxn];arr用法一样。arr[i]此时关联的就是对象。

// unique_ptr::operator->
#include <iostream>
#include <memory>
#include <vector>

struct Point { 
    int a; 
    int b; 
    Point() { a = 0; b = 0; std::cout << "Point() called." << std::endl; }
    Point(int x, int y) { a = x; b = y; std::cout << "Point(int,int) called." << std::endl; }
    ~Point() { std::cout << "~Point() called." << std::endl; }
};

int main () {

    const int maxn = 8;
    std::unique_ptr< Point[] > arr( new Point[maxn] );

    for( int i = 0; i < maxn; ++i ){
        std::cout << i << ":" << arr[i].a << "," << arr[i].b << std::endl;
    }

    return 0;
}
/*
Point() called.
Point() called.
Point() called.
Point() called.
Point() called.
Point() called.
Point() called.
Point() called.
0:0,0
1:0,0
2:0,0
3:0,0
4:0,0
5:0,0
6:0,0
7:0,0
~Point() called.
~Point() called.
~Point() called.
~Point() called.
~Point() called.
~Point() called.
~Point() called.
~Point() called.
*/

总结下unique_ptr和auto_ptr的区别:
1. 这二者都实现了独享语义,不支持共享。这是他们的共同点
2. 最大的区别在于unique_ptr支持move语义,并且同时禁止了copy语义。因为独占类的语义就是禁止copy语义的。也就是它有move constructor,而后者没有。
3. unique_ptr由于支持move语义,禁止了copy语义。作为独占资源管理类,本生就应该禁止copy语义。但是auto_ptr则没有禁止,不合理。并且还实现了move语义,使得可以作为容器元素。而后者不行。
4. 最后,前者支持动态数组,后者不支持。

shared_ptr学习

shared_ptr作为智能指针的一种,主要的用途就是来管理动态内存(dynamic memory)。我们知道如果用new和delete来管理会带来很大的问题。他们的行为类似常规指针,重要的区别他们负责自动释放所指向的对象!

shared_ptr和unique_ptr的本质区别在于:前者是基于共享语义实现的,而后者是基于独占语义实现的。这体现在他们管理底层指针的方式,shared_ptr允许多个指针指向同一个对象,并且还提供了引用计数来管理owner的数量。unique_ptr则独占所指向的对象。语义的不同体现在是否可以有复制和赋值等语义。

实例1 shared_ptr的初始化

见如下代码:初始化有两种方式。一种是采用裸指针的形式,一种是采用make_shared的方法。他们两之间在性能上存在区别,如果使用裸指针的方式,则会发生两次动态内存申请,一次是new,一次是shared_ptr为资源管理对象分配的。这两次的内容不一样,但是总归还是两次。c++ primer建议的是后者make_shared.

此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr.
采用make_shared可以一次性分配足够的内存,用来保存这个资源管理者和这个新建对象。


// unique_ptr::operator->
#include <iostream>
#include <memory>
#include <vector>

struct Point { 
    int a; 
    int b; 
    Point() { a = 0; b = 0; std::cout << "Point() called." << std::endl; }
    Point(int x, int y) { a = x; b = y; std::cout << "Point(int,int) called." << std::endl; }
    ~Point() { std::cout << "~Point() called." << std::endl; }
};

int main () {

    std::shared_ptr<Point> p; // 默认初始化智能指针保存一个空指针
    std::cout << p << std::endl; // 输出0

    std::shared_ptr<int> p1 = std::make_shared<int>(8); // make_shared形式
    std::shared_ptr<int> p11(new int(8)); // 裸指针形式
    //std::shared_ptr<int> p12 = new int(8); // illegal 禁止隐式转换
    std::cout << *p1 << std::endl;
    std::cout << *p11 << std::endl;


    return 0;
}
实例2 shared_ptr的拷贝和赋值

我们可以认为每个shared_ptr都有一个关联的计数器,通常称为引用计数。它表明了拥有堆内存的owner数量。
- 无论何时我们拷贝一个shared_ptr,引用计数都会增加
- 当我们给shared_ptr赋予一个新值或是shared_ptr析构(离开作用域),左值的计数器都会减少。

// unique_ptr::operator->
#include <iostream>
#include <memory>
#include <vector>

struct Point { 
    int a; 
    int b; 
    Point() { a = 0; b = 0; std::cout << "Point() called." << std::endl; }
    Point(int x, int y) { a = x; b = y; std::cout << "Point(int,int) called." << std::endl; }
    ~Point() { std::cout << "~Point() called." << std::endl; }
};

int main () {

    std::shared_ptr<Point> p1 = std::make_shared<Point>();
    std::cout << p1->a << "," << p1->b << std::endl;
    std::cout << "p1 ref count: " << p1.use_count() << std::endl;;

    std::shared_ptr<Point> p2 = p1; // copy semantics
    std::cout << "p2 ref count: " << p2.use_count() << std::endl;;

    std::shared_ptr<Point> p3 = std::make_shared<Point>(10,20);
    std::cout << p3->a << "," << p3->b << std::endl;
    std::cout << "p3 ref count: " << p2.use_count() << std::endl;;

    p2 = p3; // assignment semantics


    std::cout << "p1 ref count: " << p1.use_count() << std::endl;;
    std::cout << "p2 ref count: " << p2.use_count() << std::endl;;
    std::cout << "p3 ref count: " << p2.use_count() << std::endl;;

    return 0;
}
/*
Point() called.
0,0
p1 ref count: 1
p2 ref count: 2
Point(int,int) called.
10,20
p3 ref count: 2
p1 ref count: 1
p2 ref count: 2
p3 ref count: 2
~Point() called.
~Point() called.
*/
实例3 shared_ptr指向动态数组

我们先来看看shared_ptr模仿unique_ptr指向数组的形式。

// unique_ptr::operator->
#include <iostream>
#include <memory>
#include <vector>

struct Point { 
    int a; 
    int b; 
    Point() { a = 0; b = 0; std::cout << "Point() called." << std::endl; }
    Point(int x, int y) { a = x; b = y; std::cout << "Point(int,int) called." << std::endl; }
    ~Point() { std::cout << "~Point() called." << std::endl; }
};

int main () {

    int maxn = 8;

    std::unique_ptr< Point[] > p( new Point[maxn] ); // this will correctly call delete []
    //std::shared_ptr< Point[] > arr( new Point[maxn] ); // illegal call delete

    return 0;
}

编译时如果不注释,无法通过。也就是说shard_ptr无法采用类似与unique_ptr指向动态数组的形式指向动态数组。

  • 原因在哪?
    这是因为前者在对象析构时会调用delete [] ,所以可以析构动态数组空间。但是后者调用delete,只能析构单个元素。

  • 如何解决
    可以通过定制自己的析构函数对象来实现!

// unique_ptr::operator->
#include <iostream>
#include <memory>
#include <vector>

struct Point { 
    int a; 
    int b; 
    Point() { a = 0; b = 0; std::cout << "Point() called." << std::endl; }
    Point(int x, int y) { a = x; b = y; std::cout << "Point(int,int) called." << std::endl; }
    ~Point() { std::cout << "~Point() called." << std::endl; }
};

template<class T>
struct arr_deleter {
    void operator() (T * p) {
        delete [] p;
    }
};

int main () {

    int maxn = 8;

    std::shared_ptr<int> arr( new int[maxn], std::default_delete<int>() ); // 系统析构
    std::shared_ptr< int > arr1( new int[maxn], arr_deleter< int >() ); // 自定义析构

    std::shared_ptr< Point > arr2( new Point[maxn], std::default_delete< Point[] >() ); // 系统系统
    std::shared_ptr< Point > arr3( new Point[maxn], arr_deleter< Point >() ); // 自定义析构


    return 0;
}
/**
Point() called.
Point() called.
Point() called.
Point() called.
Point() called.
Point() called.
Point() called.
Point() called.
~Point() called.
~Point() called.
~Point() called.
~Point() called.
~Point() called.
~Point() called.
~Point() called.
~Point() called.
*/

上面代码自己定义了deleter,也可以调用系统的deleter。但是,这段代码还是坑很多啊!

  • 首先,对于shared_ptr而言,如果要指向数组,必须给出deleter,不管是系统的还是默认的。
  • 其次,unique_ptr和shared_ptr对于指针初始化的形式并不一样。unique_ptr是这种形式:unique_ptr<Point[]> arr(new Point[maxn]);里面给出的是数组类型。而shared_ptr则需要给出每个元素的类型,及时是指向数组的,这点和之前的用法一致。shared_ptr<Point> arr( new Point[maxn], ... );
  • shared_ptr的deleter的给出形式也是要区分是调用系统的还是调用自己定制的。如果是系统的,deleter的参数需要给出数组类型,如果是自己定制,则还是给出元素类型的。
    std::shared_ptr< Point > arr2( new Point[maxn], std::default_delete< Point[] >() ); // 系统系统
    std::shared_ptr< Point > arr3( new Point[maxn], arr_deleter< Point >() ); // 自定义析构

这里小结下:如果要用只能指针指向数组,尽量使用unique_ptr,因为通常不会具备共享语义。如果使用shared_ptr,注意模板参数类型以及deleter模板的参数类型。

实例4 shared_ptr实现共享语义

由于shared_ptr作为智能指针的一种,主要是面对堆空间进行资源的自动管理。c++ primer给出了使用动态内存的三个原因:

  • 程序不知道自己要使用多少对象
  • 程序不知道所需对象的准确类型
  • 程序需要在多个对象之间共享数据

c++primer给出了这样的一种场景:我们希望定义一个Blob,保存一组元素。但是,与容器不一样,我们希望Blob对象具有共享的语义。就是说在进行复制和赋值的时候可以进行共享。

当然,很简单的想法是通过标准容器库来实现,可是这个办法虽然简单,但是标准容器库是不支持共享语义的。所以,不能直接使用容器去保存。

这时,用到上面说的情况,当我们希望实现共享语义的时候,可以考虑使用动态内存。为了避免自己实现控制资源的回收操作,我们采用智能指针来管理这一块动态内存。由于使用共享语义,我们可以采用shared_ptr来管理。那么,这块动态内存由于要存储一组元素,我们可以用标准库容易来实现它。所以,最终的方案就是用shared_ptr来管理动态内存,这个动态内存用标准库容器来实现。所以,借助这两个工具,省掉了我们很多的麻烦。容器帮我们存储一组元素,智能指针帮我们开辟堆空间、自动回收资源、并且实现共享语义。

#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
#include <memory>

class StrBlob {
public:
    StrBlob() {}
    StrBlob( const std::initializer_list< std::string >& il ) : data(il) {}
private:
    std::vector< std::string > data;
};


class StrBlob1 { // 实现共享语义
public:

    StrBlob1() : data( std::make_shared< std::vector< std::string> >() ) {} // 也可以不调用make_shared,但是由于c++ primer建议使用make_shared。我们采用这种方法。
    StrBlob1( const std::initializer_list< std::string  >& il ) : data( std::make_shared< std::vector< std::string > >(il) ) {}

private:    
    std::shared_ptr< std::vector<std::string> > data;
};

int main() { return 0; }

完整代码如下:如果写成泛型,那就是一个共享的容器!!!

#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

class StrBlob {
public:

    StrBlob() : data( std::make_shared< std::vector< std::string> >() ) {}
    StrBlob( const std::initializer_list< std::string  >& il ) : data( std::make_shared< std::vector< std::string > >(il) ) {}
public:
    typedef std::vector<std::string>::size_type size_type;
    typedef std::vector<std::string>::iterator iterator;

public:
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    iterator begin() { return data->begin(); }
    iterator end() { return data->end(); }

    void push_back( const std::string& s ) { data->push_back(s); }
    void pop_back() { 
        check( 0, "empty strblob" );
        data->pop_back(); 
    }
public:
    std::string& front() { 
        check( 0, "empty strblob" );
        return data->front(); 
    }
    const std::string& front() const { 
        check( 0, "empty strblob" );
        return data->front(); 
    }
    std::string& back() { 
        check( 0, "empty strblob" );
        return data->back(); 
    }
    const std::string& back() const { 
        check( 0, "empty strblob" );
        return data->back(); 
    }
public:
    std::string& operator[]( size_type i ) { 
        check( i, "out of range" );
        return (*data)[i]; 
    }
    const std::string& operator[]( size_type i ) const {
        check( i, "out of range" );
        return (*data)[i];
    }
private:
    void check( size_type i, const std::string& err ) const {
        if( i >= data->size() )
            throw std::out_of_range( err );
    }

private:    
    std::shared_ptr< std::vector<std::string> > data;
};

int main() { 

    StrBlob str_list = { "kang", "lucy", "xin" };
    int sz = str_list.size();
    std::cout << "str_list:" << std::endl;
    for( int i = 0; i < sz; ++i ){
        std::cout << str_list[i] << std::endl;
    }

    StrBlob str_list_copy = str_list;
    sz = str_list_copy.size();
    std::cout << "str_list_copy:" << std::endl;
    for( int i = 0; i < sz; ++i ){
        std::cout << str_list_copy[i] << std::endl;
    }

    str_list_copy.push_back( "hello,world" );

    std::cout << "----------after copy---------- " << std::endl;

    sz = str_list.size();
    std::cout << "str_list:" << std::endl;
    for( int i = 0; i < sz; ++i ){
        std::cout << str_list[i] << std::endl;
    }

    sz = str_list_copy.size();
    std::cout << "str_list_copy:" << std::endl;
    for( int i = 0; i < sz; ++i ){
        std::cout << str_list_copy[i] << std::endl;
    }

    return 0; 
}
/*
str_list:
kang
lucy
xin
str_list_copy:
kang
lucy
xin
----------after copy---------- 
str_list:
kang
lucy
xin
hello,world
str_list_copy:
kang
lucy
xin
hello,world
*/

小小总结以下:

  • 使用get()方法只有一种情形,就是需要传递给一个不能使用shared_ptr的函数。
  • 使用get()方法获得指针之后,一定要小心,当最会一个shared_ptr销毁之后,这个指针就会实效。
  • shared_ptr如果要管理new T[maxn]的资源,记住传递给它一个deleter.

实例5 shared_ptr循环引用的问题

首先看一段代码:

// shared_ptr循环引用
#include <memory>
#include <string>
#include <iostream>

class Woman;
class Man {
public:
    Man() { std::cout << "Man() called." << std::endl; }
    explicit Man( const std::string& s ) : gender(s) { std::cout << "Man(const std::string&) called." << std::endl; }
    ~Man() { std::cout << "~Man() called." << std::endl; }

    void set_wife( std::shared_ptr< Woman >& prhs ) {
        pwife = prhs;
    }
private:
    std::string gender;
    std::shared_ptr< Woman > pwife;
};

class Woman {
public:
    Woman() { std::cout << "Woman" << std::endl; }
    explicit Woman( const std::string& s ) : gender(s) { std::cout << "Woman(const std::string&) called." << std::endl; }
    ~Woman() { std::cout << "~Woman() called." << std::endl; }

    void set_husband( std::shared_ptr< Man >& prhs ){ 
        phusband = prhs;
    }
private:
    std::string gender;
    std::shared_ptr< Man > phusband;
};

int main() { 

    std::shared_ptr< Man > pman = std::make_shared<Man>("male");
    std::shared_ptr< Woman > pwoman = std::make_shared<Woman>("female");

    pman->set_wife( pwoman );
    pwoman->set_husband( pman );

    return 0; 
}
/*
Man(const std::string&) called.
Woman(const std::string&) called.
*/

通过执行上面的代码,我们发现Man和Woman的析构函数都没有执行!也就是说堆内存没有得到回收,造成内存泄露!!!这不是我们希望看到的。

下面我们来分析以下,为什么会产生这种情况!

首先,我们先来分析类的构成。Man的内部存在一个指向Woman的shared_ptr,Woman的内部存在一个指向Man的shared_ptr。对于这种情况,当两个类的内部分别有指向彼此的shared_ptr的时候,此时这两个类构成循环引用,这回导致这两个类都无法正常的析构,造成内存泄露!

好了,那么这是为什么呢?我们继续来分析,主函数中pman和pwomen分别指向了两个Man和Women的两个堆对象。下面他们分别设置了自己内部的智能指针,pman内部的pwife指向了Women堆对象,pwomen内部的phusband指向了Man堆对象。现在来分析以下,对于Man堆对象,有pman, pwoman.phusband两个指针关联。对于Woman堆对象,有pwomen, pman.pwife两个堆对象关联。好,现在我们开始来析构,和构造顺序相反。先析构pwomen(注意,pwomen虽然指向队对象,但是它自身是栈对象),它在析构的时候需要析构phusband,首先,它会–ref_count. 此时,判断ref_count是否为0,显然ref_count现在为1,因为Man堆对象没有被析构。那么,不会继续析构它所指向的堆对象。从而,它的析构没有完成。现在,我们再析构pman,同理,它的内部有指向Woman堆对象的shared_ptr,但是由于Woman没有完成析构,所以,shared_ptr一样无法导致Woman析构。再换句话说,Man要析构,必须Woman先析构。可是Woman要析构,必须Man先析构,从而谁都无法析构。也即,构成循环引用的类都需要彼此的所指向的类先析构,自己才能析构,这回导致最后谁也析构不成,造成内存泄露。

那么,这个问题该如何解决呢?且看下一目的weak_ptr.


weak_ptr学习

c++ primer是这样形容的:

  • weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。(weak_ptr不能单独使用,要和shared_ptr一起使用,或者不使用)
  • 将一个weak_ptr绑定到shared_ptr管理的对象,不会增加shared_ptr的引用计数。
  • 一旦最后一个指向堆对象的shared_ptr被销毁,堆空间就会被释放。此时weak_ptr失效。(也就是说,及时weak_ptr指向堆对象,堆对象还是会被释放。)
  • 由于weak_ptr所指向的对象可能失效,因此不能直接通过它访问,需要首先调用lock,判断weak_ptr所指向的对象是否存在。如果,存在,lock函数返回该对象的shared_ptr

简言之,shared_ptr和weak_ptr都实现了共享语义,前者是一种强共享(强引用),后者是一种弱共享(弱引用)。强弱的区别就在于是否增加资源的属主数量。

所以,上面的代码,修改如下:

#include <memory>
#include <string>
#include <iostream>

class Woman;
class Man {
public:
    Man() { std::cout << "Man() called." << std::endl; }
    explicit Man( const std::string& s ) : gender(s) { std::cout << "Man(const std::string&) called." << std::endl; }
    ~Man() { std::cout << "~Man() called." << std::endl; }

    void set_wife( std::shared_ptr< Woman >& prhs ) {
        pwife = prhs;
    }

    std::string get_gender() const { return gender; }


private:
    std::string gender;
    std::shared_ptr< Woman > pwife;
    //std::weak_ptr< Woman > pwife;
};

class Woman {
public:
    Woman() { std::cout << "Woman" << std::endl; }
    explicit Woman( const std::string& s ) : gender(s) { std::cout << "Woman(const std::string&) called." << std::endl; }
    ~Woman() { std::cout << "~Woman() called." << std::endl; }

    void set_husband( std::shared_ptr< Man >& prhs ){ 
        phusband = prhs;
    }

    std::string get_gender() const { return gender; }
    void print_husband_gender() {

        std::shared_ptr<Man> ret = phusband.lock();
        if( ret ){ // 检查有效性
            std::cout << ret->get_gender() << std::endl;
        }

    }

private:
    std::string gender;
    //std::shared_ptr< Man > phusband;
    std::weak_ptr< Man > phusband;
};

int main() { 

    std::shared_ptr< Man > pman = std::make_shared<Man>("male");
    std::shared_ptr< Woman > pwoman = std::make_shared<Woman>("female");

    pman->set_wife( pwoman );
    pwoman->set_husband( pman );

    return 0; 
}
/*
Man(const std::string&) called.
Woman(const std::string&) called.
~Man() called.
~Woman() called.
*/

分析上面代码,发现对象正常析构。但是有一点疑惑是,栈对象的析构顺序不是和构造顺序相反嘛?怎么没有相反。
那是因为循环引用导致的,强引用的条件都是只有对方析构了,我才析构。所以,women引入若引用之后,由于不对Man引用计数增加,所以Man可以正常析构,之后才是Woman。当然,如果交换这二者的强弱关系,析构顺序交换。

小小总结下:weak_ptr主要是作为shared_ptr的若引用版本,辅助shared_ptr进行工作的。

shared_ptr安全性讨论

为了,保证循环引用问题讨论的一致性,我把shared_ptr的线程安全性放在此处讨论。主要参考了两位大神的博客。现在直接给出结论:


  1. shared_ptr的引用计数本生是安全无锁的!

问:引用计数 count++的时候,就是两条机器指令: read count,write count+1。如果两个线程同时增加shared_ptr的引用技术,应该会引起丢失一个引用计数的问题。在释放时,就会出现问题。
陈硕答:不会有问题,shared_ptr 的 count++ 是一个原子操作。在 x86 上是一条指令,lock xadd 或 lock incl
2 . 对象的读写则不是线程安全的,具体分为以下三种情况:

  • 同一个shared_ptr对象可以被多线程同时读取。【A shared_ptrinstance can be “read” (accessed using only const operations)simultaneously by multiple threads.】

  • 不同的shared_ptr对象可以被多线程同时修改(即使这些shared_ptr对象管理着同一个对象的指针)。【Different shared_ptr instances can be “written to”(accessed using mutable operations such as operator= or reset) simultaneouslyby multiple threads (even when these instances are copies, and share the samereference count underneath.) 】

  • 任何其他并发访问的结果都是无定义的。【Any other simultaneous accesses result in undefined behavior.】


智能指针注意点

  • 当指针不具有共享语义的时候,使用了shared_ptr。此时使用unique_ptr即可
  • 既然shared_ptr是具有共享语义的,那么对于多线程访问的安全性如何保证!
  • auto_ptr禁止使用,作为独占语义的智能指针,我们使用unique_ptr即可
  • 关于shared_ptr,尽量使用make_shared来进行初始化。
  • 智能指针在创建对象时,如果使用裸指针,应该确保裸指针之后都不会再使用。
  • 关于shared_ptr,使用get方法获得裸指针是非常危险的行为!禁止!
  • 关于shared_ptr,当使用shared_ptr指向动态数组的时候,需要定义自己的deleter.
  • 在使用shared_ptr时形成循环引用,使用weak_ptr来解决这个问题。
  • 关于unique_ptr,如果使用release方法之后,unique_ptr从销毁指针的责任中解脱出来,此时还需要手动销毁裸指针。
  • 关于weak_ptr,使用的时候,必须调用lock方法来判断它的有效性。

智能指针是一种工具,具体来说是一种类型,用来管理动态内存(dynamic memory or heap memory)。当然,你也可以用new和delete来管理,但是后者比较容易出bug。智能指针的行为类似常规指针,重要的区别它负责自动释放所指向的对象。C++11提供的三种智能指针分别是unique_ptr,shared_ptr和weak_ptr。区别是,前者实现了独占语义,后两者则实现了共享语义。语义上的具体体现就是是否允许复制和赋值的语义。在底层实现上是否允许多个指针指向同一个对象。shared_ptr和weak_ptr的区别是,强引用和弱引用的区别。具体表现为是否增加所指向对象的ref count.

智能指针实现

这一目记录了我对智能指针的学习。说一下我的学习过程吧,上一周还是不错的,基本上c++一些比较重要的问题都搞定了。最后还实现了智能指针作为项目实现,感觉这一块编程还是有提高的。

说一下智能指针的实现过程,我觉得我还是牛逼的,因为我没有看其他人的任何实现。就是单纯的学习了代理模式之后实现的智能指针,我的代码原创性还是比较高的。我准备先说一下代理模式,然后给出自己的实现,以及实现过程中的一些问题。

代理模式

  • 先给出定义:

    为其他对象提供一种代理以控制对这个对象的访问.

  • 举个例子:
    比如说下载服务器,每次向下载服务器发起请求之后,后者都需要去磁盘准备数据然后发送给客户端。哪怕是重复的请求,下载服务器也会这么做。但是,如果增加一个代理,代理服务器每次先判断客户端的请求的资源之前是否请求过,如果没有,则向下载服务器进行请求,然后,代理服务器回送给客户端,并进行缓存。下次如果有同样的资源请求,则直接会送给客户端。

  • UML如下:

这里写图片描述

  • Proxy
    1. 保存一个引用使得代理可以访问实体。若RealSubject和Subject的接口相同,Proxy会引用Subject,就相当于在代理类中保存一个Subject指针,该指针会指向RealSubject;
    2. 提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体;
    3. 控制对实体的存取,并可能负责创建和删除它;
    4. 其它功能依赖于代理的类型,例如:
      远程代理负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求;
      虚代理可以缓存实体的附加信息,以便延迟对它的访问;
      保护代理检查调用者是否具有实现一个请求所必须的访问权限。
  • Subject:定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy;
  • RealSubject:定义Proxy所代理的实体。

为什么要设计一个subject出来,因为在client看了,realsubject以及proxysubject,都是提供服务的。所以,可以抽象出一个公共的接口。

  • 优点

    对外部提供同一个的接口方法,代理类在对对象操作的同时,可以附加别的操作,从而在不影响外部调用的情况下,对系统进行扩展。

  • 代码

// Server.h
#ifndef SERVER_H_
#define SERVER_H_

class Server {
public:
    Server() {}
    virtual ~Server() {}
public:
    virtual void request() = 0;
};

#endif

// RealServer.h
#ifndef REAL_SERVER_H_
#define REAL_SERVER_H_
#include <iostream>
#include "Server.h"

class RealServer : public Server {
public:
    RealServer() {};
    virtual ~RealServer() {}
public:
    virtual void request() {
        std::cout << "RealServer request." << std::endl;
    }
};

#endif

// ProxyServer.h
#ifndef PROXY_SERVER_H_
#define PROXY_SERVER_H_
#include <iostream>
#include <memory>
#include "Server.h"
#include "RealServer.h"

class ProxyServer : public Server {
public:
    ProxyServer() {}
    virtual ~ProxyServer() {}
public:
    virtual void request() {
        std::cout << "ProxyServer request." << std::endl;

        // construct a real server
        if( !p_realserver ){
            p_realserver = std::unique_ptr<RealServer>( new RealServer() ) ; // move semantics
        }
        p_realserver->request();
    }
private:
    std::unique_ptr< RealServer > p_realserver;
};

#endif
// main.cpp
#include <memory>
#include <iostream>
#include "ProxyServer.h"

int main( void ) {

    ProxyServer proxy;
    proxy.request();

    return 0;
}

智能指针-代理模式实现

  • 思路

    我自己在最初设计的时候,先没想过为什么要用代理。我就只是想用raii对指针进行封装即可,这样一样可以实现对资源的回收。但是,简单的raii无法实现共享的语义。看下面的代码:
    SmartPointer<int> p(new int());
    SmartPointer<int> p1(p) ;
    SmartPointer<int> q(new int());
    p = q;

    此时,会导致p的析构,但是p1对这一切一无所知。所以,简单的raii无法实现共享的语义。unique_ptr应该可以这样实现。但是shared_ptr肯定是不行的。

    那么,此时,引入引用计数的思路,为每一个pointer关联一个cnt。这样我把他们两进行封装,如下:
    template<class T>
    class Pointer{ public:...private: int cnt; T* ptr; };

    这么写乍一看也没有问题,我在复制和赋值同时修改cnt即可,可是对于上面的那种情形,p=q执行之后,p的引用计数是可以减少,但是p1的引用计数还是没有减少,它对这一切任然一无所知。

    问题的本质出在,cnt是一个栈变量,它没有办法共享。考虑到动态内存的第三个作用就是可以实现共享语义,那么,我果断把Pointer类开到了堆空间上面,此时,我再引入一个SmartPointer的代理类,代理Pointer类的行为即可。这就是整体的设计思路。

    Pointer类相当于完成了对堆空间的管理以及引用计数,SmartPointer类则对外代理了普通指针的行为。

    当然,这里的理解大家可能有点不同。因为我实现之后看了比人的方法,基本上都是把SmartPointer实现了slot模型,一个指向对堆空间,一个指向ref的堆空间。而我的这种实现方法则被称为辅助类。大家出发的角度不同吧。

  • 代码

// Ptr.h
#ifndef PTR_H_
#define PTR_H_

// Abstract Pointer Class
template<class T>
class Ptr {
public:
    // Deference object
    virtual T& operator*() const = 0 ;

    // Defence object member
    virtual T* operator->() const = 0;
};
#endif
// Pointer.h
#ifndef POINTER_H_
#define POINTER_H_
#include <iostream>
#include "Ptr.h"

// Real Pointer Class
template<class T> 
class Pointer : public Ptr<T> {
public:
    Pointer() : ptr(NULL), ref_cnt(0) {}
    explicit Pointer( T* pp ) : ptr(pp), ref_cnt(1) { std::cout << "Pointer(int*) called." << std::endl; }
    ~Pointer() { 
        if(ptr){
            delete ptr;
            ptr = NULL;
            std::cout << "~Pointer() called." << std::endl;
        }
    }

public:
    void add_ref() { ++ref_cnt; }
    void release_ref() { --ref_cnt; }
    int get_ref() const { return ref_cnt; }

    T* get() const { return ptr; }
public:
    virtual T& operator*() const { return *ptr; }
    virtual T* operator->() const { return ptr; }
private:
    T* ptr;
    int ref_cnt;
};

#endif
// SmartPointer.h
#ifndef SMARTPOINTER_H_
#define SMARTPOINTER_H_
#include <cassert>
#include "Ptr.h"
#include "Pointer.h"

// Proxy pointer class
template<class T>
class SmartPointer : public Ptr<T> {
public:
    SmartPointer() : ppointer(NULL) {}
    explicit SmartPointer( T* pp )  {
        ppointer = new Pointer<T>( pp );
        assert(ppointer);
    }
    SmartPointer( const SmartPointer& rhs ) {
        ppointer = rhs.ppointer;

        // add ref
        ppointer->add_ref();
    }
    SmartPointer& operator=( const SmartPointer& rhs ) {
        if( this != &rhs ){

            if( !ppointer ){
                ppointer = rhs.ppointer;

                // add ref
                ppointer->add_ref();
            }
            else{

                // release ref
                ppointer->release_ref();
                if(!ppointer->get_ref() )
                    delete ppointer;

                // add ref
                ppointer = rhs.ppointer;
                ppointer->add_ref();
            }
        }
        return *this;
    }
    ~SmartPointer() {
        if( ppointer ){
            // release ref
            ppointer->release_ref();

            if(!ppointer->get_ref()){
                delete ppointer;
                ppointer = NULL;
            }
        }
    }

public:
    virtual T& operator*() const {
        assert(ppointer);       
        return ppointer->operator*(); 
    }
    virtual T* operator->() const { 
        assert(ppointer);
        return ppointer->operator->(); 
    }
public:
    T* get() const { 
        if( ppointer )
            return ppointer->get(); 
        else
            return NULL;
    }
    int use_count() const { 
        if( ppointer )
            return ppointer->get_ref(); 
        else
            return 0;
    }
public:
    operator bool() const { 
        return ppointer != NULL; 
    }
    bool operator==( const T* p_rhs ) const {
        return ppointer->get() == p_rhs;
    }
    bool operator==( const SmartPointer<T>& rhs ) const {
        return ppointer->get() == rhs.get();
    }
    bool operator!=( const T* p_rhs ) const {
        return ppointer->get() != p_rhs;
    }
    bool operator!=( const SmartPointer<T>& rhs ) const {
        return ppointer->get() != rhs.get();
    }
private:
    Pointer<T>* ppointer;
};

#endif
#include <iostream>
#include "SmartPointer.h"


void test_case1() {

    SmartPointer< int > p1( new int(10) ); // constructor
    std::cout << " p1->use_count " << p1.use_count() << std::endl;
    SmartPointer< int > p2(p1);        // copy constructor
    std::cout << " p2->use_count " << p2.use_count() << std::endl;
    SmartPointer< int > p3;            // default constructor
    std::cout << " p3->use_count " << p3.use_count() << std::endl;

    p3 = p1; // assignment operator

    std::cout << "before modify : " << std::endl;
    std::cout << *p1 << std::endl;;
    std::cout << *p2 << std::endl;;
    std::cout << *p3 << std::endl;;

    *p2 = 8;
    std::cout << "after modify : " << std::endl;
    std::cout << *p1 << std::endl;;
    std::cout << *p2 << std::endl;;
    std::cout << *p3 << std::endl;;

    SmartPointer<int> p4( new int(32) );
    std::cout << "*p4 = " << *p4 << std::endl;

    p4 = p1;
}

void test_case2(){

    SmartPointer<int> p1(new int(8));
    SmartPointer<int> p2;

    if( p1 ) std::cout << *p1 << std::endl;
    if( p2 ) std::cout << *p2 << std::endl;

    if( p1 == p2 ) std::cout << "p1==p2" << std::endl;
    if( p1 != p2 ) std::cout << "p1!=p2" << std::endl;

    SmartPointer<int> p3(p1);
    if( p1 == p3 ) std::cout << "p1==p3" << std::endl;
    if( p1 != p3 ) std::cout << "p1!=p3" << std::endl;
}

int main( void ) {

    //test_case1();
    test_case2();

    return 0;
}

上面的代码也并不是没有问题,比如,上面的代码只能管理普通指针,而不能管理动态数组的指针,主要是delete ptr。这里不是销毁动态数组的形式,按照shared_ptr的做法,可以传入一个deleter解决这个问题。


后记

这两天又复习了下智能指针,再实现部分有了不太一样的理解。对于之前的实现,解读有不同。分别说下。

  • Ptr.h
    这个类没有什么好说的,实现最基本的接口。也就是指针的两个行为。
#ifndef PTR_H_
#define PTR_H_

template< class T >
class Ptr {
public:
    // dereference object
    virtual T& operator*() = 0;

    // dereference object member
    virtual T* operator->() = 0;
};

#endif
  • Pointer.h
    1.这个类要好好说下,首先这个类实现了对动态资源最底层的管理。相当于最底层的资源管理类。由于上层是实现共享的语义,所以底层的资源是没有复制和赋值的语义。所以,资源管理类也没有复制和赋值的语义。显然,复制构造和赋值函数要禁止。这点很清楚。
    2.上层提供了共享语义,所以肯定是要支持复制和赋值语义的,但是具体实现并不是拷贝和赋值资源。而是通过引用计数实现。所以,引用计数本质上应该放在上层。但是,由于上层的都是栈对象,所以,引用技术如果在上层实现,没有办法共享。那么,把引用技术也放入资源管理层。表示引用资源的数量。然后这个资源管理类开在堆空间上实现共享。
    3.上面的思路说清楚了。再说下最初的思路,最初我是想直接在资源管理类里面管理资源和引用技术。那么,此时资源管理类变成栈对象,应用计数没法共享。所以,我把资源管理类开在了堆对象上面。再提供一个栈对象的上层。这是最初的思路。其实,比较常见的思路是在资源管理这一层,把引用计数分出去,实现一个slot模型也是可以的。

目前,就还是按照我最初的思路设计。Pointer类实现底层资源的管理,上层的引用计数也封装到Pointer里面,向高层提供支持。然后把Pointer类开到堆空间上面。实现共享。

#ifndef POINTER_H_
#define POINTER_H_
#include <iostream>
#include "Ptr.h"

template< class T >
class Pointer : public Ptr<T> {
public:
    Pointer() : ptr_(NULL), ref_cnt_(0) {}
    explicit Pointer( T* pp ) : ptr_(pp), ref_cnt_(1) {}
    virtual ~Pointer() { release_ref(); }
public:
    void add_ref() { ref_cnt_++; }
    void release_ref() { 
        ref_cnt_--;
        if(!ref_cnt_){ 
            delete ptr_; 
            ptr_ = NULL; 
        }
    }
    int get_ref() const { return ref_cnt_; }

    T* get() const { return ptr_; }
public:
    T& operator*()  { return *ptr_; }
    T* operator->() { return ptr_; }
private:
    // No copying allowed
    Pointer( const Pointer& );
    void operator=( const Pointer& );
private:
    T* ptr_;
    int ref_cnt_;
};


#endif


  • SmartPointer.h

底层提供了资源控制类,Pointer类当中含有资源的句柄,以及资源的属主(owner)数量。提供了ref();unref();方法给上层应用来控制资源的共享语义。所以上层要充分利用底层的方法。由于要实现共享语义,所以,底层Pointer对象要开成堆对象。最后还要提供判空以及判断是否相等的接口。

#ifndef SMART_POINTER_H_
#define SMART_POINTER_H_
#include <iostream>
#include <assert.h>
#include "Ptr.h"
#include "Pointer.h"    

template< class T >
class SmartPointer : public Ptr<T> {
public:
    SmartPointer() : ppointer_(NULL) {}
    explicit SmartPointer( T* pp ) {
        ppointer_ = new Pointer<T>(pp);
        assert(ppointer_);
    }
    SmartPointer( const SmartPointer<T>& rhs ){ 
        ppointer_ = rhs.ppointer_;
        assert(ppointer_);
        ppointer_->add_ref();
    }
    void operator=( const SmartPointer<T>& rhs ){
        if( this != &rhs ){
            assert(ppointer_);
            ppointer_->release_ref();

            ppointer_ = rhs.ppointer_;
            assert(ppointer_);
            ppointer_->add_ref();
        }
    }
    virtual ~SmartPointer(){
        if(ppointer_){
            ppointer_->release_ref();
        }
    }
public:
    T& operator*() { 
        assert( ppointer_ );
        return ppointer_->operator*();
    }
    T* operator->() {
        assert( ppointer_ );
        return ppointer_->operator->();
    }
public:
    T* get() const {
        assert( ppointer_ );
        return ppointer_->get();
    }
    int use_count() const {
        assert( ppointer_ );
        return ppointer_->get_ref();
    }
public:
    operator bool() const { return ppointer_ != NULL; }
    bool operator==( const T* p_rhs ) const { 
        assert( ppointer_ );
        return ppointer_->get() == p_rhs;
    }
    bool operator==( const SmartPointer<T>& rhs ) const {
        assert(ppointer_);
        return ppointer_->get() == rhs.get();
    }
    bool operator!=( const T* p_rhs ) const {
        return !operator==(p_rhs);
    }
    bool operator!=( const SmartPointer<T>& rhs ) const {
        return !operator==(rhs);
    }
private:
    Pointer<T>* ppointer_;
};

#endif
  • main.cc
    这个文件提供方法的测试函数。
#include "Pointer.h"
#include "SmartPointer.h"
struct Point{
    int x;
    int y;
    Point(){ std::cout << "Point() called." << std::endl; }
    Point( int xx, int yy ) : x(xx), y(yy) { std::cout << "Point(int,int)" << std::endl; }
    ~Point(){ std::cout << "~Point() called." << std::endl; }
};

void test_Pointer(){

    Pointer<Point> p;
    Pointer<Point> p1(new Point());

    p1->x = 5;
    p1->y = 3;
}

void test_SmartPointer1(){

    SmartPointer<Point> p1( new Point(3, 4) );
    std::cout << p1->x << "," << p1->y << std::endl;

    SmartPointer<Point> p2(p1);
    std::cout << p2->x << "," << p2->y << std::endl;
    std::cout << "p1 use count :" << p1.use_count() <<std::endl;
    std::cout << "p2 use count :" << p2.use_count() <<std::endl;

    SmartPointer<Point> p3( new Point(7, 8) );
    std::cout << p3->x << "," << p3->y << std::endl;

    p2 = p3;
    std::cout << p2->x << "," << p2->y << std::endl;

    SmartPointer<int> pp( new int(3) );
    std::cout << *pp << std::endl;
}

void test_SmartPointer2(){

    SmartPointer<Point> p1( new Point(3,4) );
    if( p1 ){
        std::cout << p1->x << "," << p1->y << std::endl;
    }
    Point* p2 = new Point(3,4);
    Point* p3 = p1.get();

    if( p1 == p2 ) 
        std::cout << "p1==p2" << std::endl;
    if( p1 != p2 )
        std::cout << "p1!=p2" << std::endl;

    if( p1 == p3 ) 
        std::cout << "p1==p3" << std::endl;
    if( p1 != p3 )
        std::cout << "p1!=p3" << std::endl;

    SmartPointer<Point> p4(p1);
    if( p1 == p4 ) 
        std::cout << "p1==p4" << std::endl;
    if( p1 != p4 )
        std::cout << "p1!=p4" << std::endl;

    SmartPointer<Point> p5(new Point(7,8));
    if( p1 == p5 ) 
        std::cout << "p1==p5" << std::endl;
    if( p1 != p5 )
        std::cout << "p1!=p5" << std::endl;
}

int main(void){
    test_SmartPointer2();
    return 0;
} 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值