Skip to content

Chapter 14 Smart Pointers

标准库中的智能指针
  • std::unique_ptr:独占所有权,默认首选
  • std::shared_ptr:共享所有权,内部维护引用计数
  • std::weak_ptr:不拥有对象,只用于观察 shared_ptr 管理的对象
  • std::auto_ptr(C++11 起已废弃)

目标

  • 介绍如何维护对象的引用计数
  • 被共享对象由 UCObject 保存计数
  • 智能指针 UCPointer<T> 负责在赋值、复制、析构时维护计数

1 Reference Counting

共享对象内部维护一个计数器,记录当前有多少个指针正在共享它。

Reference counts in action

String x("abcdef");
String y = x;        // shallow copy
x = "Hello world";   // copy on write

  • xy 初始可共享同一份底层表示,引用计数为 2
  • 若其中一个被写入修改,则可分离成两份数据

每个可共享对象有一个计数器,初始值通常是 0,指针赋值时需要先对旧对象减计数,再对新对象加计数。

Have to do the following

p->decrement();
p = q;
p->increment();
  • UCObject:负责引用计数
  • UCPointer<T>:智能指针模板
  • String:对外暴露的接口类(Envelope)
  • StringRep:真正持有字符数据的表示类(Letter)

2 Class UCObject

Reusing reference counting

#include <assert.h>

class UCObject {
public:
    UCObject() : m_refCount(0) {}
    virtual ~UCObject() { assert(m_refCount == 0); }
    UCObject(const UCObject&) : m_refCount(0) {}

    void incr() { m_refCount++; }
    void decr();
    int references() { return m_refCount; }
private:
    int m_refCount;
};

decr()

inline void UCObject::decr() {
    m_refCount -= 1;
    if (m_refCount == 0) {
        delete this;
    }
}

delete this 只对堆对象安全,不要对栈对象这么做。

3 Class UCPointer

示例

template <class T>
class UCPointer {
private:
    T* m_pObj;

    void increment() { if (m_pObj) m_pObj->incr(); }
    void decrement() { if (m_pObj) m_pObj->decr(); }
public:
    UCPointer(T* r = 0) : m_pObj(r) { increment(); }
    ~UCPointer() { decrement(); }

    UCPointer(const UCPointer<T>& p);
    UCPointer& operator=(const UCPointer<T>&);
    T* operator->() const;
    T& operator*() const { return *m_pObj; }
};

拷贝构造

template <class T>
UCPointer<T>::UCPointer(const UCPointer<T>& p) {
    m_pObj = p.m_pObj;
    increment();
}

赋值运算

template <class T>
UCPointer<T>& UCPointer<T>::operator=(const UCPointer<T>& p) {
    if (m_pObj != p.m_pObj) {
        decrement();
        m_pObj = p.m_pObj;
        increment();
    }
    return *this;
}

4 Envelope / Letter Pattern

Envelope and Letter

  • 信封(Envelope):提供对外的保护与接口
  • 信件(Letter):承载实际的内容数据

  • String 是外层信封,负责对外接口
  • StringRep 是内部信件,负责真实数据与实现细节
  • StringRep 继承 UCObject,因此天然带引用计数
  • String 通过 UCPointer<StringRep> 持有共享表示

示例:String

class String {
public:
    String(const char*);
    ~String();
    String(const String&);
    String& operator=(const String&);
    int operator==(const String&) const;
    String operator+(const String&) const;
    int length() const;
    operator const char*() const;
private:
    UCPointer<StringRep> m_rep;
};

StringRep

class StringRep : public UCObject {
public:
    StringRep(const char*);
    ~StringRep();
    StringRep(const StringRep&);
    int length() const { return strlen(m_pChars); }
    int equal(const StringRep&) const;
private:
    char* m_pChars;
};

StringRep Implementation

StringRep::StringRep(const char* s) {
    if (s) {
        int len = strlen(s) + 1;
        m_pChars = new char[len];
        strcpy(m_pChars, s);
    } else {
        m_pChars = new char[1];
        *m_pChars = '\0';
    }
}

StringRep::~StringRep() {
    delete[] m_pChars;
}

深拷贝表示层

StringRep::StringRep(const StringRep& sr) {
    int len = sr.length();
    m_pChars = new char[len + 1];
    strcpy(m_pChars, sr.m_pChars);
}

int StringRep::equal(const StringRep& sp) const {
    return strcmp(m_pChars, sp.m_pChars) == 0;
}

String Implementation

String::String(const char* s)
    : m_rep(new StringRep(s)) {}

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==(const String& s) const {
    return m_rep->equal(*s.m_rep);
}

int String::length() const {
    return m_rep->length();
}

外层 String 的拷贝构造和赋值几乎可以“交给智能指针处理”,这样对外接口仍保持值语义,但底层表示可以共享。

Critique

  • 优点
    • UCPointer 统一维护引用计数
    • UCObject 隐藏计数细节
    • StringRep 只关心字符串数据本身
    • UCObjectUCPointer 可复用
  • 缺点
    • 比裸指针慢
    • 设计有侵入性:被管理对象必须继承 UCObject
    • 标准库中的 std::shared_ptr 提供了非侵入式方案