2016-11-30 93 views
0

考慮以下情況,其中一個基類規定,可以在派生類中重新實現的功能:平滑過渡

template<typename Impl> 
class Base { 
public: 
    void f(double& value, double param) 
    { value = 0.0; } 
}; 

class Implementation : public Base<Implementation> { 
public: 
    void f(double& value, double param) 
    { value = param; } 
}; 

int main() 
{ 
    Implementation imp; 
    double value = 0.0; 
    imp.f(value, 1.0); 
} 

假設我想改變功能f在簽名基類到double f(double param)並將其用於Base和派生類。我想在不立即破壞所有實現類的情況下執行此更改。相反,實現類的提供者應該得到棄用警告。我想出了以下情況:

template<typename Impl> 
class Base { 
public: 
    double f(double param) __attribute__ ((deprecated)) 
    { 
     double v = 0.0; 
     static_cast<Impl*>(this)->f(v, param); 
     return v; 
    } 
    void f(double& value, double param) 
    { value = 0.0; } 
}; 

// class Implementation as before 

int main() 
{ 
    Implementation imp; 
    double value = imp.f(1.0); 
} 

其結果是,在Base版本的f只應如果fImplementation與新的簽名重新實現,得到所需的棄用警告調用。

很顯然,這並不能編譯,因爲在Implementation陰影的f定義fBase

error: no matching function for call to ‘Implementation::f(double)’ 
    double value = imp.f(1.0); 

一個解決辦法是增加using

class Implementation : public Base<Implementation> { 
public: 
    using Base<Implementation>::f; 
    void f(double& value, double param) 
    { value = param; } 
}; 

這將迫使提供者Implementation立即改變他的代碼,這正是應該首先避免的。另一種可能性是在新簽名中更改名稱f。我也想避免這個,因爲f在我的真實使用案例中有一個非常好的名字。

所以我的問題是:我可以進行簽名更改

  • 使得Implementation提供商獲得棄用警告,但他們的代碼不會立即斷裂,即,在不改變Implementation,並
  • 而不必重命名f
+2

只是一個問題,與問題無關:爲什麼接口中的方法不是標記爲「虛擬」?如果方法不能被覆蓋,接口的用途是什麼?或者,更好的是,它爲什麼會拋出一個異常,而不是被標記爲純虛擬的(這是用C++編寫接口的首選方式)。 –

+0

「使用」解決方案有什麼問題? –

+0

這是具有更復雜的類相互依賴性的真實案例的簡化版本。通過使用CRTP,靜態綁定優於動態綁定。我儘可能地把它煮沸,同時保持本質。 –

回答

0

我可以構建從jrok's answer對我的作品的問題Check if a class has a member function of a given signature的解決方案:

#include <iostream> 
#include <type_traits> 

template<typename, typename T> 
struct hasFunctionF {}; 

template<typename C, typename Ret, typename... Args> 
struct hasFunctionF<C, Ret(Args...)> { 
private: 
    template<typename T> 
    static constexpr auto check(T*) 
    -> typename 
     std::is_same< 
      decltype(std::declval<T>().f(std::declval<Args>()...)), 
      Ret // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
     >::type; // attempt to call it and see if the return type is correct 

    template<typename> 
    static constexpr std::false_type check(...); 

    typedef decltype(check<C>(0)) type; 

public: 
    static constexpr bool value = type::value; 
}; 

template<typename Impl> 
class Base { 
public: 
    template<class T = Impl> 
    __attribute__ ((deprecated)) 
    const typename std::enable_if_t<hasFunctionF<T, void(double&, double)>::value, double> g(double param) 
    { 
     double v = 0.0; 
     static_cast<Impl*>(this)->f(v, param); 
     return v; 
    } 

    template<class T = Impl> 
    const typename std::enable_if_t<hasFunctionF<T, double(double)>::value, double> g(double param) 
    { 
     return static_cast<Impl*>(this)->f(param); 
    } 
}; 

class OldImplementation : public Base<OldImplementation> { 
public: 
    void f(double& value, double param) 
    { value = param; } 
}; 

class NewImplementation : public Base<NewImplementation> { 
public: 
    double f(double param) 
    { return param; } 
}; 

int main() 
{ 
    OldImplementation oldImp; 
    double value = oldImp.g(1.0); 
    std::cout << "old: " << value << std::endl; 
    NewImplementation newImp; 
    std::cout << "new: " << newImp.g(1.0) << std::endl; 
} 

我還是要在基類中引入一個新的函數名g,並使用該名稱調用。但我不認爲這可以在不改變用戶代碼的情況下避免。用戶代碼是OldImplementation,不需要立即更改。編譯使用舊界面的用戶代碼只會提供棄用警告。這正是我想要實現的。