平滑过渡

问题描述:

考虑以下情况,其中一个基类规定,可以在派生类中重新实现的功能:平滑过渡

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,静态绑定优于动态绑定。我尽可能地把它煮沸,同时保持本质。 –

我可以构建从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,不需要立即更改。编译使用旧界面的用户代码只会提供弃用警告。这正是我想要实现的。