跳轉到內容

更多 C++ 慣用法/寫時複製

來自華夏公益教科書,為開放世界提供開放書籍

寫時複製

[編輯 | 編輯原始碼]

實現延遲複製最佳化。就像延遲初始化一樣,只在需要的時候才執行操作,以提高效率。

也稱為

[編輯 | 編輯原始碼]
  • COW (寫時複製)
  • 延遲複製

複製物件有時會導致效能下降。如果物件經常被複制,但在以後很少被修改,寫時複製可以提供顯著的最佳化。為了實現寫時複製,一個指向實際內容的智慧指標用於封裝物件的價值,並且在每次修改時,都會檢查物件的引用計數;如果物件被引用多次,則會在修改之前建立內容的副本。

解決方案和示例程式碼

[編輯 | 編輯原始碼]
#ifndef COWPTR_HPP
#define COWPTR_HPP

#include <memory>

template <class T>
class CowPtr
{
    public:
        typedef std::shared_ptr<T> RefPtr;

    private:
        RefPtr m_sp;

        void detach()
        {
            T* tmp = m_sp.get();
            if( !( tmp == 0 || m_sp.unique() ) ) {
                m_sp = RefPtr( new T( *tmp ) );
            }
        }

    public:
        CowPtr(T* t)
            :   m_sp(t)
        {}
        CowPtr(const RefPtr& refptr)
            :   m_sp(refptr)
        {}
        const T& operator*() const
        {
            return *m_sp;
        }
        T& operator*()
        {
            detach();
            return *m_sp;
        }
        const T* operator->() const
        {
            return m_sp.operator->();
        }
        T* operator->()
        {
            detach();
            return m_sp.operator->();
        }
};

#endif

這種寫時複製的實現是通用的,但除了必須透過智慧指標解除引用來引用內部物件的不便之外,它至少有一個缺點:那些返回對其內部狀態引用的類,比如

char & String::operator[](int)

可能會導致意想不到的行為。[1]

考慮以下程式碼片段

CowPtr<String> s1 = "Hello";
char &c = s1->operator[](4); // Non-const detachment does nothing here
CowPtr<String> s2(s1); // Lazy-copy, shared state
c = '!'; // Uh-oh

最後一行程式碼的目的是修改原始字串s1,而不是副本,但作為副作用,s2也意外被修改了。

一個更好的方法是編寫一個自定義的寫時複製實現,該實現封裝在我們想要延遲複製的類中,對使用者來說是透明的。為了解決上述問題,可以將那些已經將內部狀態的引用傳遞出去的物件標記為“不可共享”——換句話說,強制複製操作深度複製物件。作為最佳化,可以在任何不傳遞內部狀態引用(例如,void string::clear())的非常量操作之後將物件恢復為“可共享”,因為客戶端程式碼期望這些引用會被無效化。[1]

已知用途

[編輯 | 編輯原始碼]
[編輯 | 編輯原始碼]

參考資料

[編輯 | 編輯原始碼]
  1. a b Herb Sutter,《更優秀的 C++》,Addison-Wesley 2002 年 - 第 13-16 條
華夏公益教科書