OOP Lec13: Smart Pointer

Smart Pointer

内存回收

C++ 的垃圾回收:仍然可以作为学术论文的主题,说明没解决

主要问题:1. 没有单根结构 2. 类和对象的联系很弱

OCaml:学术语言

智能指针的出现整合了以下内容:

  1. 模板
  2. 继承、虚函数
  3. 引用计数

引用计数

定义

引用计数的定义:一个对象被几个引用共享,这些引用的个数。

  • 需要指针来修改引用计数
  • 一般称为 UCObject(Use-Counted Object),其智能指针称为 UCPointer
    • 智能指针由另一个类定义
    • 使用模板编程
    • 必须重载 operator->operator*

运行时引用计数

以自定义的 String 类为例。

String s1("abcdef");
String s2 = s1;// shadow copy
s1 = "Hello World";// copy on write

在创建对象 s1 时,字符串空间需要一个空间来保存计数,表示该字符串被多少个指针引用。初始值为 $0$。

拷贝构造时不复制字符串本身,而是对共享的引用计数自增。

指针指向新的对象时,旧对象的引用计数要自减,新对象的引用计数自增。

如果一个对象的引用计数归零,这个对象就可以被销毁了(不可能再被寻址)。

代码实现

一个智能指针的实现,需要四个类:

  1. UCObject:维护引用计数
  2. String Rep:is-a UCObject,维护字符串内容,是可共享的
  3. UCPointer:has-a String Rep,是 UCObject 的智能指针,类模板编程
  4. String:has-a UCObject,维护 public 接口

UCObject 只维护引用计数,所以构造时总是赋 $0$​。

在引用计数归零时销毁自己。

#include <assert.h>
class UCObject{
public:
    UCObject();
    virtual ~UCObject();
    UCObject(const UCObject& rhs);
    void incr();
    void decr();
    int reference();
private:
    int m_refcount;
};
UCObject::UCObject(): m_refcount(0){}
UCObject::UCObject(const UCObject& rhs): m_refcount(0){}
virtual UCObject::~UCObject(){
    assert(m_refcount == 0);
}
void UCObject::incr(){
    m_refcount++;
}
void UCObject::decr(){
    m_refcount--;
    if(!m_refcount){
        delete this;// legal, but don't use it afterwards
    }
}
int UCObject::reference(){
    return m_refcount;
}

UCPointer 维护引用计数的操作(自增自减)。

template<typename T>
class UCPointer{
public:
    UCPointer(T* ref = nullptr): m_ptr(ref){this->increment();}
    ~UCPointer(){this->decrement();}
    UCPointer(const UCPointer<T>& rhs);
    UCPointer<T>& operator =(const UCPointer<T>& rhs);
    T* operator ->()const;
    T& operator *()const;
private:
    T* m_ptr;
    void increment(){if(m_ptr) m_ptr->incr();}
    void decrement(){if(m_ptr) m_ptr->decr();}
};
template<typename T>
UCPointer<T>::UCPointer(const UCPointer<T>& rhs){// a new reference
    m_ptr = rhs.m_ptr;
    this->increment();
}
template<typename T>
UCPointer<T>& UCPointer<T>::operator =(const UCPointer<T>& rhs){
    if(m_ptr != rhs.m_ptr){
        this->decrement();
        rhs.increment();
        m_ptr = rhs.m_ptr;
    }
    return *this;
}
template<typename T>
T& UCPointer<T>::operator *()const{
    return *m_ptr;
}
template<typename T>
T* UCPointer<T>::operator ->()const{
    return m_ptr;
}

opearter->() 是一元运算符,其结果应该也支持 -> 运算。

Ellipse elly(200F, 300F);
UCPointer<Shape> ptr(&elly);
p->render();// Ellipse::render(this = &elly)

其他一些 C++ 可以重载的运算符:

  • []:下标寻址
  • ():调用函数
  • ->():指针调用
  • *():一元的指针解引用

String Rep 继承 UCObject,维护字符串内容的空间。

class StringRep: public UCObject{
public:
    StringRep(const char* str);
    ~StringRep();
    StringRep(const StringRep& rhs);
    int length()const{return strlen(m_str);}
    int equal(const StringRep& s)const;
    operator const char*()const;
private:
    char *m_str;
    void operator =(const StringRep& rhs){}// forbidden assignment operator
};
StringRep::StringRep(const char* str){
    if(str != nullptr){
        int len = strlen(str) + 1;
        m_str = new char[len];
        strcpy(m_str, str);
    }
    else{
        m_str = new char[1];
        *m_str = '\0';
    }
}
StringRep::~StringRep(){
    delete[] m_str;
}
StringRep::StringRep(const StringRep& rhs){
    int len = rhs.length();
    m_str = new char[len + 1];
    strcpy(m_str, rhs.m_str);
}
int StringRep::equal(const StringRep& s)const{
    return (strcmp(m_str, s.m_str) == 0);
}
Stringrep::operator const char*()const{
    return m_str;
}

通过将一个运算符重载为 private,可以达到在外部禁用此运算符的效果。

String 类可以直接利用智能指针的特性管理内存,不需要自己管理字符串空间。

class String{
public:
    String(const char* str);
    ~String();
    String(const String& rhs);
    String& operator =(const String& rhs);
    int operator ==(const String& s)const;
    String operator +(const String& s)const;
    int length()const;
    operator const char*()const;
private:
    UCPointer<StringRep> m_rep;
};
String::String(const char* str): m_rep(nullptr){
    m_rep = new StringRep(str);
}
String::~String(){}
String::String(const String& s): m_rep(s.m_rep){}
String& String::operator =(const String& s){
    m_rep = s.m_rep;
    return *this;
}
int String::operator ==(cons String& s)const{
    return m_rep->equal(*s.m_rep);
}
int String::length()const{
    return m_rep->length();
}

小结

  • UCPointer 维护了引用计数
  • UCObject 包装了引用计数修改的内容,使得 String 类不需要再维护,非常简洁
  • StringRep 只负责字符串内容的维护,而不关心其他内容
  • UCObjectUCPointer 可以重新利用
    • 但如果出现 UCPointer 的循环指向,则环上的内容都会不能释放(引用计数不能归零)

拓展:STL 中的 auto_ptr

语法

<memory> 库中,模板:

template<typename _Tp>
class auto_ptr{
public:
    typedef _Tp element_type;
    // constructors & destructors
    explicit auto_ptr(element_type* __p = 0) throw(): _M_ptr(__p){}
    auto_ptr(auto_ptr& __a) throw(): _M_ptr(__a.release()){}
    template<typename _Tp1>
    auto_ptr(auto_ptr<_Tp1>& __a)throw(): _M_ptr(__a.release()){}
    auto_ptr(auto_ptr_ref<element_type> __ref) throw(): _M_ptr(__ref._M_ptr){}
    ~auto_ptr(){delete _M_ptr;}
    // assignment operations
    auto_ptr& operator=(auto_ptr& __a) throw();
    template<typename _Tp1>
    auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw();
    auto_ptr& operator=(auto_ptr_ref<element_type> __ref) throw();
    // dereference operation
    element_type& operator*() const throw();
    element_type* operator->() const throw();

    element_type* get() const throw();// get the pointer
    element_type* release() throw();// discard the pointer and return the content
    void reset(element_type* __p = 0) throw();// reset the pointer

    template<typename _Tp1>
    operator auto_ptr_ref<_Tp1>() throw(){return auto_ptr_ref<_Tp1>(this->release());}
    template<typename _Tp1>
    operator auto_ptr<_Tp1>() throw(){return auto_ptr<_Tp1>(this->release());}
private:
    _Tp* _M_ptr;
} _GLIBCXX11_DEPRECATED_SUGGEST("std::unique_ptr");// suggest use std::unique_pt

测试代码

#include <iostream>
#include <memory>
using namespace std;
class Test{
public:
    int id;
    string info;
    Test(int pid = 0): id(pid), info(){
        cout << "construct id:" << id << endl;
    }
    ~Test(){
        cout << "destruct id:" << id << endl;
    }
    void print(){
        cout << "print infomation:" << info << endl;
    }
private:
};
signed main(int argc, char **argv){
    auto_ptr<Test> ptr(new Test(131));
    if(ptr.get() != nullptr){
        ptr->print();
        ptr.get()->info = "Hello World!";
        ptr->print();
        (*ptr).print();
        ptr->print();
    }
    return 0;
}

运行结果:

construct id:131
print infomation:
print infomation:Hello World!
print infomation:Hello World!
print infomation:Hello World!
destruct id:131

可以看到 new Test(131) 的对象最后被释放了。

弊端

内存泄漏风险

auto_ptrrelease()reset() 实现:

element_type* release() throw(){
    element_type* __tmp = _M_ptr;
    _M_ptr = 0;
    return __tmp;
}
void reset(element_type* __p = 0) throw(){
    if (__p != _M_ptr){
        delete _M_ptr;
        _M_ptr = __p;
    }
}

在我们想要主动销毁一个智能指针时,如果仅调用 release() 而不使用别的指针接受返回值,就会产生内存泄漏。

reset() 的参数在不阅读源码的情况下不易理解。

所有权的转移

auto_ptr 赋值运算符和拷贝构造的实现:

template<typename _Tp1>
auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw(){
    reset(__a.release());
    return *this;
}
auto_ptr(auto_ptr& __a) throw(): _M_ptr(__a.release()){}

如果一个 auto_ptr 被赋给了另一个,系统上认为指向的对象的所有权转移了,原指针变为空,有时候这并不是我们想要的。

同样地,如果在函数参数中未使用引用传递,那么进入函数后调用拷贝构造,原来的智能指针就变为空了。

如果要使用 STL 的智能指针,建议学习:std::unique_ptr - cppreference.comstd::shared_ptr - cppreference.com

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇