跳转到内容

更多 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
[编辑 | 编辑源代码]

参考文献

[编辑 | 编辑源代码]
华夏公益教科书