covariant return type and boost::shared_ptr
covariant return type翻譯爲協變返回值,意義就是在虛函數的返回值上,可以使用子類對象,而不必是基類對象.下面是一個例子:
struct Object
{
public:
virtual ~Object()
{}
};
struct Cloneable: public Object
{
public:
virtual ~Cloneable()
{}
virtual Object* Clone() const = 0;
};
struct Foo : public Cloneable
{
virtual Foo* Clone() const //注意這裏
{
return new Foo();
}
};
我們看到Foo的Clone函數返回的是Foo對象,而不是Object對象.顯然,C++的這個特點,對於設計繼承類層次來說,在某些情況下使用非常方便,因爲你不必被強迫使用dynamic_cast或者static_cast來進行類型轉換了.
boost::shared_ptr是boost智能指針中最爲常用的一個,它是內存資源或者其他資源的正確釋放的有力工具. boost::shared_ptr使用在函數返回值當中,也有很好的作用.例如上面的Clone函數的返回值是一個指針,那麼這個指針的生命週期的維護就是一個問題.一般來說,這需要使用者和實現者之間有一個很好的協議,這個協議的嚴格遵守才能保證避免資源的泄露,這個問題也是C++世界中一個非常棘手的問題.引進boost::shared_ptr,可以在很大的程度上解決這個問題,代碼如下:
struct Object
{
public:
virtual ~Object()
{}
};
typedef boost::shared_ptr<Object> ObjectPtr;
struct Cloneable: public Object
{
public:
virtual ~Cloneable()
{}
virtual ObjectPtr Clone() const = 0;
};
struct Foo;
typedef boost::shared_ptr<Foo> FooPtr;
struct Foo : public Cloneable
{
virtual FooPtr Clone() const
{
return boost::shared_ptr<Foo>(new Foo());
}
};
資源的釋放由智能指針本身維護,用戶不必再擔心資源的泄露問題.這是一個非常好的方案.
但是,C++的世界不是如此的完美,上面的代碼是不能編譯成功的,原因是:雖然Object和Foo之間存在繼承關係,但是ObjectPtr和FooPtr之間不存在繼承關係,這意味着協變返回值在這裏不起作用.
爲了解決這個問題,在boost的新聞組上http://lists.boost.org/boost-users/2003/02/2996.php 給出瞭如下的方案(注意這裏是針對我們的例子修改以後的):
struct Object
{
public:
virtual ~Object()
{}
};
typedef boost::shared_ptr<Object> ObjectPtr;
struct Cloneable: public Object
{
public:
virtual ~Cloneable()
{}
virtual ObjectPtr DoClone() const = 0;
};
struct Foo;
typedef boost::shared_ptr<Foo> FooPtr;
struct Foo : public Cloneable
{
virtual ObjectPtr DoClone() const
{
return boost::shared_ptr<Foo>(new Foo());
}
FooPtr Clone() const
{
return boost::dynamic_pointer_cast<X>(this-> DoClone ());
}
};
這個方案在一定程度上解決了問題,但是我個人非常不認同這個方案,主要的理由如下:
1. Cloneable類型是一個接口類,一般來說,接口的抽象程度比較高,使用範圍也很廣.接口一般是用來抽象概念的,上面改變接口函數的做法,在一定的程度上傷害了這個概念.試想,如果你是Cloneable的最初設計者,你會把接口聲明爲DoClone嗎?這種技術妨礙概念設計的做法,是應該竭力避免的.
2. 即便是從純粹技術的角度說,這種做法也存在問題.上面的例子是兩層繼承設計,當然在實際中也可能是三層或者更多的層次,我們就以三層爲例,爲了使用這種技術,還得設定一個新的名稱,難道是DoDoClone?!顯然,這個的技術會隨着層次的加深而越發的不可接受.所以說,這個技術的擴展性比較差.
其實,我認爲更好的方法在boost中已經使用,這個方法是:
1) 上層的接口設計保持不變,仍然是從概念出發,例如Cloneable的接口函數名稱仍然是Clone,返回值類型是ObjectPtr;
2) 下層的接口直接繼承上層的接口,如果是實現類,那麼並不使用協變返回值的技術,仍然是返回ObjectPtr;
3) 提供一個模板化的全局函數,實現向上轉型.
上面例子的做法是:
struct Object
{
public:
virtual ~Object()
{}
};
typedef boost::shared_ptr<Object> ObjectPtr;
struct Cloneable: public Object
{
public:
virtual ~Cloneable()
{}
virtual ObjectPtr Clone() const = 0;
};
struct Foo;
typedef boost::shared_ptr<Foo> FooPtr;
struct Foo : public Cloneable
{
virtual ObjectPtr Clone() const
{
return ObjectPtr <Foo>(new Foo());
}
};
template<class X> shared_ptr<X> clone(X const & x)
{
shared_ptr<X> px = boost::dynamic_pointer_cast<X>(x.Clone());
assert(px);
return px;
}
其實這裏也可以使用boost:: static_pointer_cast.