可以理解,函数既不能模板化也不能虚拟化。 但是可能存在超级智能设计模式。
我的目标是拥有一个看起来像:
的功能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;,但这似乎没有帮助(或者我错过了什么?)
答案 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");
但是我们失去了将YAML_config::get
声明为模板的能力 - 除了类型之外,实现都是一样的,但是我们不能用模板覆盖虚函数。
因此,既然我们已经弥合了从模板到虚函数的差距以实现多态性,那么让我们将虚拟函数的差距缩小回模板,以便恢复我们的优秀API。这可以通过在Configuration
和YAML_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 {};
}
};
答案 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>
需要使用的类型子集,那么肯定会有更高效的解决方案。