在CRTP系列的最后一節中,讓我們看一下一種實現,它使編寫CRTP類變得更加容易。
擺脫static_cast
在CRTP基類中重復編寫static_casts很快變得很麻煩,因為這并沒有給代碼增加太多含義:
template <typename T>
struct NumericalFunctions
{
void scale(double multiplicator)
{
T& underlying = static_cast<T&>(*this);
underlying.setValue(underlying.getValue() * multiplicator);
}
...
};
排除這些static_casts會很好。 這可以通過將基礎類型轉換到更高的層次結構層次來實現:
template <typename T>
struct crtp
{
T& underlying() { return static_cast<T&>(*this); }
T const& underlying() const { return static_cast<T const&>(*this); }
};
另外,它還處理了我們還沒有提到的底層對象是const的情況。
可以通過以下方式使用此助手:
template <typename T>
struct NumericalFunctions : crtp<T>
{
void scale(double multiplicator)
{
this->underlying().setValue(this->underlying().getValue() * multiplicator);
}
...
};
注意,static_cast消失了,出現了this->。 沒有它,代碼將無法編譯。 確實,編譯器不確定underlying是在哪里聲明的。 即使在模板類crtp中聲明了,從理論上講,也無法保證該模板類不會針對特定類型重寫和特化,可能不會暴露underlying方法。 因此,C ++中會忽略模板基類中的名稱。
使用this->是將它們包含在函數作用域內的一種方法。 還有其他方法可以做到,盡管可以說它們并不適合這種情況。 無論如何,你都可以在Effective C ++的43條中閱讀有關此主題的所有信息。
無論如何,上面的代碼使你不必編寫static_casts,當static_casts有很多的時候,它們會變得非常繁瑣。
如果你僅通過CRTP類添加一項功能,那么所有這些方法均有效,但如果要加更多功能,它就不好使了。
使用CRTP添加更多功能
為了便于說明,我們將CRTP類分為兩類:一類用于縮放,另一類用于對值進行平方:
template <typename T>
struct Scale : crtp<T>
{
void scale(double multiplicator)
{
this->underlying().setValue(this->underlying().getValue() * multiplicator);
}
};
template <typename T>
struct Square : crtp<T>
{
void square()
{
this->underlying().setValue(this->underlying().getValue() * this->underlying().getValue());
}
};
然后把這兩個功能加到Sensitivity類中:
class Sensitivity : public Scale<Sensitivity>, public Square<Sensitivity>
{
public:
double getValue() const { return value_; }
void setValue(double value) { value_ = value; }
private:
double value_;
};
乍一看似乎可以,但是只要我們調用任一基類的方法,它就不會成功編譯!
error: 'crtp<Sensitivity>' is an ambiguous base of 'Sensitivity'
這是因為我們這里是一個菱形繼承:
我最初嘗試使用虛繼承來解決此問題,但很快就放棄了,因為我沒有找到如何簡單地做到這一點并且不會影響crtp類使用處的方法。如果您有任何建議,請講出來!
另一種方法是通過使每個功能(縮放,平方)都從其自己的crtp類繼承,從而避開菱形繼承(聽起來是個好主意)。這可以通過…CRTP來實現!
實際上,我們可以向crtp類中添加一個與基類相對應的模板參數。 請注意,添加了crtpType模板參數。
template <typename T, template<typename> class crtpType>
struct crtp
{
T& underlying() { return static_cast<T&>(*this); }
T const& underlying() const { return static_cast<T const&>(*this); }
private:
crtp(){}
friend crtpType<T>;
};
請注意,template參數不僅是類型名,而且是template <typename>類。 這僅表示參數不僅是類型,而且是模板本身,以其名稱被忽略的類型為模板。 例如,crtpType可以是Scale。
此參數僅在區分類型時使用,并且在crtp的實現中不使用該參數(友元聲明中的技術檢查除外)。 這種未使用的模板參數稱為“幻像類型”(或更準確地說,我們可以將其稱為“幻像模板”)。
現在,類層次結構如下所示:
我們可以繼續下去了。
CRTP上的CRTP。 模板真有趣。