About

多态泛型不两全 - 面向对象中的协变与逆变

在面向对象语言中, 子类覆盖父类中的实例成员函数时, 可以酌情修改返回值类型, 使之变得更加具体. 比如在 C++ 中, 下面的代码是可行的.

Code Snippet 0-0

class base {
public:
   virtual base* copy() const
   {
       return new base;
   }
};

class inherit: public base {
   virtual inherit* copy() const
   {
       return new inherit;
   }
};

因为 inherit* 类型可以无压力转换为 base* 类型, 所以这样做并不会破坏多态机制. 这种做法称之为协变 (covariance).

然而协变一方面满足着面向对象设计的种种需求时, 另一方面与泛型的协作却非常糟糕, 比如如果尝试使用自动指针取代上述代码中的返回值类型就不正确了

Code Snippet 0-1

class base {
public:
   virtual std::unique_ptr

copy() const
   {
       return new base;
   }
};

class inherit: public base {
   virtual std::unique_ptr
copy() const
   {
       return new inherit;
   }
};

编译这一段代码会得到类似如下的错误

invalid covariant return type for 'virtual std::unique_ptr inherit::copy() const'

简而言之, std::unique_ptrstd::unique_ptr 并无实质上的继承关系, 因此无法如此重定义子类函数的返回类型.

这并不是语言层面出现的疵漏, 如果这确实可行, 反而还会引起问题, 举个例子

Code Snippet 0-2

class fruit {};
class apple: public fruit {public: void do_something_as_apple();};
class banana: public fruit {public: void do_something_as_banana();};

int main(void)
{
   std::unique_ptr a(new apple);

   /* 如果子类的自动指针对象是可以为父类的自动指针的引用直接引用
      也就是说下面这一句合法 */
   std::unique_ptr& f = a;

   /* 接下来利用 f 来重置内部的裸指针 */
   f.reset(new banana);

   /* 由于 f 就是 a 的引用, 因此 a 中的裸指针指向了
      与 apple 类型不兼容的 banana 的实例 */
   a->do_something_as_apple(); // 謎の結果
   return 0;
}

因此, 这种利用自动指针绕着弯子来造错误的行为是绝对不被允许的. 此外, 下面的这种赋值方式 (虽然明显看起来很怪异) 也是不被允许的

int main(void)
{
   inherit* i;
   base*& b = i; // 跪
   return 0;
}

之前的一篇文章也有简单介绍到, 不过当时说的是父类的二级指针不能被子类二级指针所赋值.

除了指针之外, 可以想象数组, 包括 STL 容器也会纷纷中枪, 比如 std::vector 类型的引用是无法引用 std::vector 类型的对象的.

在 Java 语言中, 为了缓解这个问题, 引入了泛型通配符. 这个名字听起来就很语死早, 又是泛又是通配, 不过程序设计世界就是这么充满纠结. 下面是 Java 中的解决方案

Permanent Link: /p/490/

Post tags:

C++

Java

面向对象程序设计

All 1

. Back to Tobary book
Tobary book - Copyright (C) ZhePlus @ Bit Focus
About