zoukankan      html  css  js  c++  java
  • 管理C++类中的指针成员

    图论看的头大…于是翻了翻抱佛脚必备书:《程序员面试宝典》,这书编的确实不怎么样,边边角角的题目有点多,有些题目的解答思路很不清晰,当做题库看看也就罢了。今天翻到一道标准容器复制含有指针成员的类导致重复解析的问题,专门回忆了下这方面的知识,在这里做个总结。

    C++最讽刺的地方就是“用指针实现了面向对象”这点,所以C++压根不是什么面向对象,说是面向指针更恰当一点。内存管理这块一直是C++最复杂的地方之一,也是很多人讨厌C++的最大原因之一(我猜C风格字符串是另外一个原因之一)。复制(或使用隐含复制的操作)含有指针成员的类,必须对指针成员做一些特殊的处理,主要方法包括以下几种:

    ①值型类,在复制指针成员时,重新分配内存,复制对象。使得不同的对象之间完全无耦合,配合C++11引入的右值引用,高效又省事。

    ②使用C++11中引入的新技术,包括shared_ptr,unique_ptr和weak_ptr(#include <memory>)。

    ③自己定义智能指针类,实现②中的功能。

    我个人最喜欢还是用①,这么干最简单,如果加上垃圾回收器,基本就成了java。但是对于大规模数据运算的类,还是不要这么干为妙。方法②是比较方便的方法,前提是对这三个smart pointer有比较好的理解,而且你个人喜欢这个风格的书写。(其实我看着感觉不是很爽…)

    其中unique_ptr被设计出来是用来取代auto_ptr的,shared_ptr就是一个使用计数类,配合weak_ptr来使用。详细的介绍可以直接看微软的文档,或者这个翻译的版本:http://kheresy.wordpress.com/2012/03/05/c11_smartpointer_p2/

    如果讨厌stl的智能指针类,最后的方法就是自己实现这个玩意。C++prime 4th在13.5.1和15.8.1两节介绍了两个方法来实现使用计数,总结一下:

     1 class U_Ptr;
     2 class HasPtr
     3 {
     4 public:
     5     HasPtr(int *p,int i):ptr(new U_Ptr(p)),val(i){}
     6     HasPtr(const HasPtr &orig):
     7         ptr(orig.ptr),val(orig.val)
     8     {
     9         ++ptr->use;
    10     }
    11     HasPtr& operator=(const HasPtr&);
    12     ~HasPtr(){if (--ptr->use==0)delete ptr;}
    13     int *get_ptr()const{return ptr->ip;}
    14     int get_int()const{return val;}
    15     void set_ptr(int *p){ptr->ip=p;}
    16     void set_int(int i){val=i;}
    17     int get_ptr_val()const{return *ptr->ip;}
    18     int set_ptr_val(int i)const{*ptr->ip=i;}
    19 private:
    20     U_Ptr *ptr;
    21     int val;
    22 };
    23 class U_Ptr
    24 {
    25     friend class HasPtr;
    26     int *ip;
    27     size_t use;
    28     U_Ptr(int *p):ip(p),use(1){}
    29     ~U_Ptr(){delete ip;}
    30 };
    1 HasPtr& HasPtr::operator=(const HasPtr& other)
    2 {
    3     ++other.ptr->use;
    4     if(--ptr->use==0)delete ptr;
    5     ptr=other.ptr;
    6     val=other.val;
    7     return *this;
    8 }

    这里使用了一个计数类,用来实际存放本来应该在HasPtr中保存的指针,然后又使用了友元。这种设计风格会破坏掉类的封装,也就是所谓侵入式智能指针,故一般不推荐。

     1 #include <exception>
     2 #include <iostream>
     3 class BasePtr;
     4 class HandlePtr
     5 {
     6 public:
     7     HandlePtr():ptr(nullptr),use(new size_t(1)){}
     8     HandlePtr(const HandlePtr& i)
     9         :ptr(i.ptr),use(i.use)
    10     {
    11         ++*use;
    12     }
    13     HandlePtr(HandlePtr&& i)
    14         :ptr(i.ptr),use(i.use)
    15     {
    16     }
    17     HandlePtr(const BasePtr&);
    18     ~HandlePtr(){decr_use();}
    19     HandlePtr& operator=(const HandlePtr&);
    20     const BasePtr* operator->()const
    21     {
    22         if(ptr)return ptr;
    23         else throw std::logic_error("unbound HandlePtr");
    24     }
    25     const BasePtr& operator*()const
    26     {
    27         if(ptr)return *ptr;
    28         else throw std::logic_error("unbound HandlePtr");
    29     }
    30 private:
    31     BasePtr* ptr;
    32     size_t *use;
    33     void decr_use()
    34     {
    35         if(--*use==0){delete ptr;delete use;}
    36     }
    37 };
    38 class BasePtr 
    39 {
    40 public:
    41     virtual BasePtr* clone()const
    42     {
    43         return new BasePtr(*this);
    44     } 
    45 };
    1 HandlePtr::HandlePtr(const BasePtr& other)
    2     :ptr(other.clone()),use(new size_t(1)){}

    上面则是更好的,也是比较常见的指针管理方式:使用句柄类。句柄类中包含指向管理的类和其子类的指针,句柄类重载了箭头和解引用操作符,使其指向实际管理的指针,完成动态绑定。句柄类的可以直接由基类引用初始化, 但是由于基类引用可能指向子类,所以必须定义虚克隆来返回实际的类型。

    以上两个方法的核心思想都是“引用计数”,而shared_ptr也是这个原理。令人蛋疼的是,boost引入了大量智能指针,仅仅是掌握这些智能指针的用法就够头疼的,好在shared_ptr几乎可以解决所有的问题,所以它得到了最广泛的应用,掌握shared_ptr基本上可以解决大部分memory leak的问题(如果不需要考虑引用计数,可以使用unique_ptr)。

    我试着用shared_ptr完成了书中sales_item的例子,如下:

     1 #include <string>
     2 #include <ostream>
     3 class Basket;
     4 class Item_base{
     5 public:
     6     Item_base(const std::string& book="",
     7                 double sales_price=0.0):
     8     isbn(book),price(sales_price){}
     9     std::string book()const
    10     {
    11         return isbn;
    12     }
    13     virtual double net_price(std::size_t n)const
    14     {
    15         return n*price;
    16     }
    17     virtual ~Item_base(){}
    18 private:
    19     std::string isbn;
    20 protected:
    21     double price;
    22 };
    23 
    24 class Bulk_item:public Item_base{
    25 public:
    26     double net_price(std::size_t n)const;
    27 private:
    28     std::size_t min_qty;
    29     double discount;
    30 };
    31 void print_total(std::ostream &os,const Item_base &item,std::size_t n);
    #include "common.h"
    #include <memory>
    #include <set>
    using namespace std;
    double Bulk_item::net_price(std::size_t cnt)const
    {
        if(cnt>=min_qty)
            return cnt*(1-discount)*price;
        else
            return cnt*price;
    }
    void print_total(ostream &os,const Item_base &item,size_t n)
    {
        os<<"ISBN:"<<item.book()
            <<"\tnumber sold:"<<n<<"\ttotal price:"
            <<item.net_price(n)<<endl;
    }
    inline bool
        compare(const shared_ptr<Item_base>& lb1,const shared_ptr<Item_base>& lb2)
    {
        return lb1->book() < lb2->book();
    }
    class Basket{
        typedef bool (*Comp)(const shared_ptr<Item_base>&,const shared_ptr<Item_base>&);
        multiset<shared_ptr<Item_base>,Comp> items;
    public:
        typedef multiset<shared_ptr<Item_base>,Comp> set_type;
        typedef set_type::size_type size_type;
        typedef set_type::const_iterator const_iter;
        Basket():items(compare){}
        void add_item(const shared_ptr<Item_base> &pItem)
        {
            items.insert(pItem); 
        }
        size_type size(const shared_ptr<Item_base> &i)
        {
            return items.count(i);
        }
        double total()const
        {
            double sum=0.0;
            for(auto iter=items.begin();iter!=items.end()
                ;iter=items.upper_bound(*iter))
            {sum+=(*iter)->net_price(items.count(*iter));}
            return sum;
        }
    };

    大部分情况下,shared_ptr的使用比较简单,当做一个自己书写的句柄类使用即可。但是shared_ptr有一些陷阱,比如不能传递数组(但是可以用vector代替,或者指定删除器);不要在函数实参中初始化;最好不要把this指针传给shared_ptr,如果需要返回this,可以考虑使用继承std::enable_shared_from_this,然后返回shared_from_this()(但是这么做之前必须已经有一个正常产生的shared_ptr来存放这个返回的指针);在可能出现循环引用时,使用weak_ptr打断这种循环;如果是命名对象,最好不要使用new而使用make_shared;另外大量使用shared_ptr会产生碎片,需要自定义分配器进行内存管理。

  • 相关阅读:
    四 闭包函数、装饰器
    三 名称空间与作用域
    二 函数对象、函数嵌套
    一 函数定义
    函数路线
    Django_rest_framework分页
    Django Rest framework序列化流程
    Django Rest framework的限流实现流程
    mysql 数据库查看表的信息
    java JDBC编程流程步骤
  • 原文地址:https://www.cnblogs.com/livewithnorest/p/2680411.html
Copyright © 2011-2022 走看看