界面变化的平滑过渡

时间:2016-11-30 11:43:11

标签: c++ c++14

考虑以下情况,其中基类规定了可以在派生类中重新实现的函数:

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时才会调用f Implementation版{@ 1}},从而产生所需的弃用警告

显然,这不会编译,因为fImplementation的定义会影响f中的Base

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

1 个答案:

答案 0 :(得分: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,不得立即更改。使用旧接口编译用户代码仅提供弃用警告。这正是我想要实现的目标。