如何为类的继承层次结构的getter创建统一接口?

Mar*_*dik 3 c++ templates pointers c-preprocessor

我认为没有代码的任何解释都会更加模糊.所以这里的代码是我尽量保持一切尽可能简单的代码.

#include <vector>
#include <iostream>

class WithParametersBase
{
public:
    WithParametersBase();

    double getX() const {return 0.0;}
    double getY() const {return 1.0;}

    //let's say I want to access these members using an unified interface:

    double getParameter(int index) const;

    // For example index == 0 means getX and index == 1 means getY.
    // I could implement it for example like this:

protected:

    void addGetter(double (WithParametersBase::* getter)()const)
    {
        getters_.push_back(getter);
    }

    std::vector<double (WithParametersBase::*)()const> getters_;
};

WithParametersBase::WithParametersBase()
{
    addGetter(&WithParametersBase::getX);
    addGetter(&WithParametersBase::getY);
}

double WithParametersBase::getParameter(int index) const
{
    return (this->*(getters_[index]))();
}
Run Code Online (Sandbox Code Playgroud)

确实有效.有了测试程序:

int main(int argc, char *argv[])
{
   WithParametersBase base;

   std::cout << base.getParameter(0)
             << base.getParameter(1) << std::endl;

   return 0;
}
Run Code Online (Sandbox Code Playgroud)

打印输出是正确的:

01
Run Code Online (Sandbox Code Playgroud)

但是如果我想扩展这个类:

class WithParametersDerived : public WithParametersBase
{
public:
    WithParametersDerived();
    double getZ() const  {return 2.0;} // A new getter
};

WithParametersDerived::WithParametersDerived()
{
    // I want to integrate the new getter into the previous interface
    addGetter(&WithParametersDerived::getZ); 
}
Run Code Online (Sandbox Code Playgroud)

如果我打电话:

WithParametersDerived derived;
std::cout << derived.getParameter(2) << std::endl;
Run Code Online (Sandbox Code Playgroud)

我想得到一个

2
Run Code Online (Sandbox Code Playgroud)

我无法编译程序.我收到一个错误:

error: no matching function for call to
'WithParametersDerived::addGetter
(double (WithParametersDerived::*)()const)'
Run Code Online (Sandbox Code Playgroud)

这是合理的,但我不知道如何实现它.

我希望派生类的创建者能够只添加新的getter.我知道,它在某种程度上不能在运行时完成所有这些,但我没有看到模板解决方案或预处理器解决方案.如果您有任何建议,请告诉我.什么!

Emi*_*ier 6

我会回避你为什么需要这样一个计划,并专注于如何.

您可以使用a std::function<double ()>,而不是成员函数指针,它是具有签名的任何可调用实体的通用包装器double foo().要创建std::function<double ()>成员函数和对象实例,请std::bind按如下方式使用:

std::function<double ()> callback =
    std::bind(&Class::memberFunction, objectInstancePointer);
Run Code Online (Sandbox Code Playgroud)

如果您不使用C++ 11,则std :: functionstd :: bind在Boost中也可用作boost :: functionboost :: bind.这些Boost文档大部分(如果不是完全)适用于他们的C++ 11版本.

std::vector您可以使用a std::map来按名称索引getter,而不是a .这可能比维护参数ID号的中心列表更实用.

如果您的参数类型不同double,那么您可能需要考虑使用boost :: anyboost :: variant作为返回类型.

下面是一个完整的工作示例使用std::function,std::bind以及std::map:

#include <cassert>
#include <map>
#include <iostream>
#include <functional>

class WithParametersBase
{
public:
    WithParametersBase()
    {
        addGetter("X", std::bind(&WithParametersBase::getX, this));
        addGetter("Y", std::bind(&WithParametersBase::getY, this));
    }

    virtual double getX() const {return 0.0;}
    virtual double getY() const {return 1.0;}

    // Access parameter by name
    double getParameter(const std::string& name) const
    {
        auto getterIter = getters_.find(name);
        assert(getterIter != getters_.end());
        return getterIter->second();
    }

protected:
    typedef std::function<double ()> ParameterGetter;
    typedef std::map<std::string, ParameterGetter> GetterMap;

    void addGetter(const std::string& name, const ParameterGetter& getter)
    {
        getters_[name] = getter;
    }

    GetterMap getters_;
};

class WithParametersDerived : public WithParametersBase
{
public:
    WithParametersDerived()
    {
        addGetter("Z", std::bind(&WithParametersDerived::getZ, this));

        // Override base class getX
        addGetter("X", std::bind(&WithParametersDerived::getX, this));
    }

    double getX() const {return 3.0;} 
    double getZ() const {return 2.0;} // A new getter
};

int main(int argc, char *argv[])
{
    WithParametersBase base;
    WithParametersDerived derived;
    WithParametersBase& polymorphic = derived;

    std::cout << base.getParameter("X")
              << base.getParameter("Y")
              << polymorphic.getParameter("X")
              << polymorphic.getParameter("Y")
              << polymorphic.getParameter("Z") << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这种方法的缺点是每个WithParametersBase(或后代)实例都包含一个GetterMap.如果您有大量此类对象,则所有这些对象的内存开销GetterMaps可能是不合需要的.


这是一个更有效的解决方案,可以消除std::functionstd::bind.常规函数指针和静态成员函数用于getter回调.请求参数的对象实例作为参数传递给这些静态成员函数.在派生类型中,在调用实际获取的成员函数之前,首先将实例引用向下转换为派生类型.

现在GetterMap 每个类只有一个而不是每个对象.注意在方法中使用"构造首次使用"的习惯用法,getters()以避免静态初始化命令惨败.

这个解决方案的缺点是,为每个派生的类创建了更多的样板代码WithParametersBase.有可能使用模板减少样板代码的数量(绝对可以使用宏).

#include <cassert>
#include <map>
#include <iostream>

class WithParametersBase
{
public:
    virtual double getX() const {return 0.0;}
    virtual double getY() const {return 1.0;}

    // Access parameter by name
    double getParameter(const std::string& name) const
    {
        auto getterIter = getters().find(name);
        assert(getterIter != getters().end());
        return getterIter->second(*this);
    }

protected:
    typedef double (*ParameterGetter)(const WithParametersBase& instance);
    typedef std::map<std::string, ParameterGetter> GetterMap;

    static double xGetter(const WithParametersBase& instance)
    {
        return instance.getX();
    }

    static double yGetter(const WithParametersBase& instance)
    {
        return instance.getY();
    }

    static GetterMap makeGetterMap()
    {
        GetterMap map;
        map["X"] = &WithParametersBase::xGetter;
        map["Y"] = &WithParametersBase::yGetter;
        return map;
    }

    virtual const GetterMap& getters() const
    {
        // Not thread-safe. Use std::call_once to make thread-safe.
        static GetterMap map = makeGetterMap();
        return map;
    };
};

class WithParametersDerived : public WithParametersBase
{
public:
    double getX() const {return 3.0;} 
    double getZ() const {return 2.0;} // A new getter

protected:
    static double zGetter(const WithParametersBase& instance)
    {
        // It's reasonably safe to assume that 'instance' is of type
        // WithParametersDerived, since WithParametersDerived was the one
        // that associated "Z" with this callback function.
        const WithParametersDerived& derived =
            dynamic_cast<const WithParametersDerived&>(instance);
        return derived.getZ();
    }

    static GetterMap makeGetterMap()
    {
        // We "inherit" the getter map from the base class before extending it.
        GetterMap map = WithParametersBase::makeGetterMap();
        map["Z"] = &WithParametersDerived::zGetter;
        return map;
    }

    virtual const GetterMap& getters() const
    {
        // Not thread-safe. Use std::call_once to make thread-safe.
        static GetterMap map = makeGetterMap();
        return map;
    };
};

int main(int argc, char *argv[])
{
    WithParametersBase base;
    WithParametersDerived derived;
    WithParametersBase& polymorphic = derived;

    std::cout << base.getParameter("X")
              << base.getParameter("Y")
              << polymorphic.getParameter("X")
              << polymorphic.getParameter("Y")
              << polymorphic.getParameter("Z") << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)