C++ 協變式的回傳型態 - Covariant Return Type

在 Prototype pattern 中,T* clone(); method 通常會寫成 return new Cat(*this) 利用 copy constructor 來建立一個複本,然後再傳回複本的位址。

Prototype pattern
class Animal{ 
... 
  virtual Animal* clone(){ return new Animal(*this); };
...
}; 
class Cat : public Animal{ 
... 
  virtual Animal* clone(){ return new Cat(*this); };
... 
};

但是這樣子的寫法會在一個地方出現問題,就是單純地複製給 Cat*指標 ,像這樣:

無法安全 downcast 所引發的 compile error
Cat* cat1 = new Cat();
Animal* cat2 = cat1->clone(); // works fine
Cat* cat3 = cat1->clone(); // error: cannot initialize a variable of type 'Cat *' with an rvalue of type 'Animal*'

因為 compiler 無法得知由 比較小 的 Animal 是否能夠安全的下轉到 比較大 的 Cat,所以就會噴出訊息,除非我們明確告知 compiler 從 Cat 複製出去的 Animal 一定會是一隻 Cat 不要擔心,像是這樣:

提供 compiler 型別的資訊
Animal* animal = cat1->clone();
Cat* cat3 = static_cast<Cat*>(animal);

那麼若只是想單純複製一隻 Cat,就要這樣大張旗鼓地動用 static_cast,是否太麻煩了點?
這時候 Covariant Return Type 就非常有用處了

依照 C++ 標準 n3242.pdf 10.3.7 中 covariant return type 定義

The return type of an overriding function shall be either identical to the return type of the overridden function or covariant with the classes of the functions. If a function D::f overrides a function B::f, the return types of the functions are covariant if they satisfy the following criteria:

  • both are pointers to classes, both are lvalue references to classes, or both are rvalue references to classes112
  • the class in the return type of B::f is the same class as the class in the return type of D::f, or is an unambiguous and accessible direct or indirect base class of the class in the return type of D::f
  • both pointers or references have the same cv-qualification and the class type in the return type of D::f has the same cv-qualification as or less cv-qualification than the class type in the return type of B::f.

我們可以將 Cat 的 clone method 回傳 Cat*,像是這樣:

用 covarient 來多載 return type
class Animal{ 
... 
  virtual Animal* clone(){ return new Animal(*this); };
...
}; 
class Cat : public Animal{ 
... 
  virtual Cat* clone(){ return new Cat(*this); };
... 
};

本來在覆寫 virtual function 時,只能夠多載其參數,多載 return type 會引發 compile error,但是 c++ 在標準中允許你的 Derived::clone 的 return type 所指向的 class(Cat) 是繼承 Base::clone 的 return type 所指向的 class(Animal) 的話,就可以多載它。

那麼之後的 client 就不需要引入不必要的轉型,很高雅地複製他所要的貓了,而且多型的操作上也是完全沒有問題。

Happy Ending
Cat* cat1 = new Cat();
Animal* cat2 = cat1->clone(); // implicitly upcast to Animal* from Cat*
Cat* cat3 = cat1->clone(); // elegantly clone cat

參考:
c++ draft: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf
wiki: http://en.wikipedia.org/wiki/Covariant_return_type
HOWTO: http://www.lwithers.me.uk/articles/covariant.html

comments powered by Disqus