跳轉到內容

更多 C++ 慣用法/成員模板強制轉換

來自華夏公益教科書,開放的書籍,開放的世界

成員模板強制轉換

[編輯 | 編輯原始碼]

透過允許類模板參與其引數化型別所享有的相同隱式型別轉換(強制轉換),來提高類模板介面的靈活性。

也稱為

[編輯 | 編輯原始碼]

將兩個型別之間的關係擴充套件到使用這些型別專門化的類模板通常很有用。例如,假設類 D 派生自類 B。指向 D 型別物件的指標可以分配給指向 B 的指標;C++ 隱式支援這一點。但是,由這些型別組成的型別不共享組成型別的關係。這同樣適用於類模板,因此 Helper<D> 物件通常不能分配給 Helper<B> 物件。

class B {};
class D : public B {};
template <class T>
class Helper {};

B *bptr;
D *dptr;
bptr = dptr; // OK; permitted by C++

Helper<B> hb;
Helper<D> hd; 
hb = hd; // Not allowed but could be very useful

在某些情況下,這種轉換很有用,例如允許從 std::unique_ptr<D> 轉換為 std::unique_ptr<B>。這非常直觀,但在不使用成員模板強制轉換慣用法的情況下是不可支援的。

解決方案和示例程式碼

[編輯 | 編輯原始碼]

在類模板中定義成員模板函式,這些函式依賴於引數型別支援的隱式型別轉換。在以下示例中,模板化建構函式和賦值運算子適用於任何型別 U,只要允許從 U * 初始化或賦值 T *

template <class T>
class Ptr
{
  public:
    Ptr () {}

    Ptr (Ptr const & p)
      : ptr (p.ptr)
    {
      std::cout << "Copy constructor\n";
    }

    // Supporting coercion using member template constructor.
    // This is not a copy constructor, but behaves similarly.
    template <class U>
    Ptr (Ptr <U> const & p)
      : ptr (p.ptr) // Implicit conversion from U to T required
    {
      std::cout << "Coercing member template constructor\n";
    }

    // Copy assignment operator.
    Ptr & operator = (Ptr const & p)
    {
      ptr = p.ptr;
      std::cout << "Copy assignment operator\n";
      return *this;
    }

    // Supporting coercion using member template assignment operator.
    // This is not the copy assignment operator, but works similarly.
    template <class U>
    Ptr & operator = (Ptr <U> const & p)
    {
      ptr = p.ptr; // Implicit conversion from U to T required
      std::cout << "Coercing member template assignment operator\n";
      return *this;
    } 

    T *ptr;
};

int main (void)
{
   Ptr <D> d_ptr;
   Ptr <B> b_ptr (d_ptr); // Now supported
   b_ptr = d_ptr;         // Now supported
}

此慣用法的另一個用途是允許將指向類的指標陣列分配給指向該類基類的指標陣列。鑑於 D 派生自 B,D 物件 **是** B 物件。但是,D 物件陣列 **不是** B 物件陣列。由於切片,C++ 中禁止這樣做。放寬對指向指標陣列的此規則可能會有所幫助。例如,指向 D 的指標陣列應該可以分配給指向 B 的指標陣列(假設 B 的解構函式是虛擬的)。應用此慣用法可以實現這一點,但需要格外小心以防止將指向一種型別的指標陣列複製到指向派生型別的指標陣列。可以專門化成員函式模板或使用 SFINAE 來實現這一點。

以下示例使用模板化建構函式和賦值運算子,期望 Array<U *> 僅在元素型別不同時才允許複製指標陣列。

template <class T>
class Array
{
  public:
    Array () {}
    Array (Array const & a)
    {
      std::copy (a.array_, a.array_ + SIZE, array_);
    }

    template <class U>
    Array (Array <U *> const & a)
    {
      std::copy (a.array_, a.array_ + SIZE, array_);
    }

    template <class U>
    Array & operator = (Array <U *> const & a)
    {
      std::copy (a.array_, a.array_ + SIZE, array_);
    }

    enum { SIZE = 10 };
    T array_[SIZE];
};

許多智慧指標,例如 std::unique_ptr、std::shared_ptr,都採用了這種慣用法。

注意事項

[編輯 | 編輯原始碼]

在實現成員模板強制轉換慣用法時,一個常見的錯誤是在引入模板化建構函式和賦值運算子時,沒有提供非模板複製建構函式或複製賦值運算子。如果類沒有聲明覆制建構函式和複製賦值運算子,編譯器將自動宣告它們,這會導致在使用此慣用法時出現隱藏且不明顯的錯誤。

已知用法

[編輯 | 編輯原始碼]
  • std::unique_ptr
  • std::shared_ptr
[編輯 | 編輯原始碼]

參考文獻

[編輯 | 編輯原始碼]
華夏公益教科書