c ++虚拟模板化函数

时间:2017-01-25 14:10:47

标签: c++ templates polymorphism virtual type-erasure

可以理解,函数既不能模板化也不能虚拟化。 但是可能存在超级智能设计模式。

我的目标是拥有一个看起来像:

的功能
void configure(const Configuration &config){
     double stuff = config.get<double>("stuff");
     int thing = config.get<int>("thing");
     // rest of the code
}

理想情况下,我可以传递各种配置对象,例如从文件或数据库中读取的对象。

这里是一个使用yaml-cpp的具体配置类的示例(剥离到最小)(即使你不知道yaml-cpp我也能理解):

class YAML_config : public Configuration {
public:
  YAML_config(std::string file_path){
     this->node = YAML::LoadFile(file_path);
  }
  template<typename T> T get(std::string key){
     return this->node[key].as<T>();
  }
private:
  YAML::Node node;

问题是:什么是类配置的合适代码?

这里有一些显示意图的无效代码:

class Configuration {
    virtual template<typename T> T get(std::string key)=0;
}

如果这只是一个糟糕的开始,我应该研究其他方法吗?我检查过&#34;输入erasure&#34;,但这似乎没有帮助(或者我错过了什么?)

2 个答案:

答案 0 :(得分:3)

看起来你有一小组可能的类型,所以我建议将一组虚拟功能与非虚拟调度模板组合在一起:

template <class T>
struct tag { };

class Configuration {
public:
    template <class T>
    T get(std::string key) {
        return get_(tag<T>{}, std::move(key));
    }

protected:
    virtual int get_(tag<int>, std::string key) = 0;
    virtual double get_(tag<double>, std::string key) = 0;
    virtual std::string get_(tag<std::string>, std::string key) = 0;
};

class YAML_config : public Configuration {
    int get_(tag<int>, std::string key) override { /* ... */ }
    double get_(tag<double>, std::string key) override { /* ... */ }
    std::string get_(tag<std::string>, std::string key) override { /* ... */ }
};

用法:

YAML_config cfg;
auto s = cfg.get<int>("hello");

See it live on Coliru

但是我们失去了将YAML_config::get声明为模板的能力 - 除了类型之外,实现都是一样的,但是我们不能用模板覆盖虚函数。

因此,既然我们已经弥合了从模板到虚函数的差距以实现多态性,那么让我们将虚拟函数的差距缩小回模板,以便恢复我们的优秀API。这可以通过在ConfigurationYAML_config类之间插入CRTP来完成:它的作用是生成覆盖函数。

注意:get_虚拟函数现在称为getBridge。我添加了一些宏来减少重复。例如,可以使用Boost.PP进一步考虑这些因素。

class ConfigurationBase {

// ...

#define DECLARE_CONFIG_BRIDGE(T) \
    virtual T getBridge(tag<T>, std::string key) = 0;

    DECLARE_CONFIG_BRIDGE(int)
    DECLARE_CONFIG_BRIDGE(double)
    DECLARE_CONFIG_BRIDGE(std::string)

#undef DECLARE_CONFIG_BRIDGE
};

template <class Derived>
class Configuration : public ConfigurationBase {

    // Hide ConfigurationBase::get so we don't get
    // infinite recursion if we forget an implementation
    // in the derived class.
    template <class>
    void get(...) = delete;

#define OVERRIDE_CONFIG_BRIDGE(T) \
    T getBridge(tag<T>, std::string key) override { \
        return dThis()->template get<T>(std::move(key)); \
    }

    OVERRIDE_CONFIG_BRIDGE(int)
    OVERRIDE_CONFIG_BRIDGE(double)
    OVERRIDE_CONFIG_BRIDGE(std::string)

#undef OVERRIDE_CONFIG_BRIDGE

    Derived *dThis() {
        return static_cast<Derived*>(this);
    }
};

class YAML_config : public Configuration<YAML_config> {
public:
    template <class T>
    T get(std::string) {
        return {};
    }
};

See it live on Coliru

答案 1 :(得分:1)

我已经将my answer改编为今天早些时候使用类型擦除和RTTI来获得虚拟模板化函数效果的类似问题。正如我在那里指出的那样,如果你不能或不想使用RTTI,可以使用Boost.TypeIndex

基本实现看起来像这样(只需填写你的YAML库):

#include <functional>
#include <typeindex>
#include <unordered_map>

class config {
public:
    template <typename T>
    T get(char const* key) {
        T value = {};
        auto it = getters.find(type_index<T>());
        if (it != getters.end()) {
            it->second(&value, key);
        }
        return value;
    }

protected:
    template <typename T, typename Getter>
    void register_getter(Getter getter) {
        getters[type_index<T>()] = [getter](void* value, char const* key) {
            *static_cast<T*>(value) = getter(key);
        };
    }

private:
    template <typename T>
    static std::type_index type_index() {
        return std::type_index(typeid(std::remove_cv_t<T>));
    }

    std::unordered_map<std::type_index, std::function<void (void*, char const*)>> getters;
};

用法看起来像这样(请注意,如果实际上不需要config作为基类,则可以使用组合而不是继承):

#include <iostream>

class yaml_config : public config {
public:
    yaml_config() {
        register_getter<int>([](char const* key) {
            return 42;
        });
        register_getter<float>([](char const* key) {
            return 3.14f;
        });
    }
};

int main() {
    yaml_config cfg;

    std::cout << cfg.get<int>("foo") << "\n";
    std::cout << cfg.get<float>("bar") << "\n";
    std::cout << cfg.get<short>("baz") << "\n";
}

输出:

42
3.14
0

在此特定实现中,T必须是默认可构造的;如果这是不可接受的,您可以使用std::any代替void*。此外,在未注册适当的getter的情况下返回默认值。您可能希望抛出异常,或返回std::optional<T>std::pair<T, bool>,以将这些情况与实际映射到特定密钥的默认值区分开来。

此解决方案的优点是子类可以为任何类型注册getter。但是,如果您知道config::get<T>需要使用的类型子集,那么肯定会有更高效的解决方案。

相关问题