0

我知道标题没有意义,找不到更好的东西。

我需要为 SQlite 表提供 C++ 接口,我可以在其中存储键/值/类型配置设置,例如

    Key     |   Value    |   Type
PATH        | /path/to/  |  STRING
HAS_FEATURE |    Y       |  BOOLEAN
REFRESH_RATE|    60      |  INTEGER

为了简单和灵活的目的,数据模型将值作为字符串托管,但提供了一个列来保留原始数据类型。

这就是我想象的客户端调用此类 c++ 接口的方式。

Configuration c;
int refreshRate = c.get<int>("REFRESH_RATE");

// Next line throws since type won't match
std::string refreshRate = c.get<std::string>("REFRESH_RATE");

这就是我想象的实现它的方式(我知道代码不会按原样编译,将其视为伪 C++,我更多地质疑设计而不是这里的语法)

class Parameter
{
    public:
        enum KnownTypes 
        {
            STRING = 0,
            BOOLEAN,
            INTEGER,
            DOUBLE,
            ...
        }

        std::string key;
        std::string value;
        KnownTypes type;
}

class Configuration 
{
    public:
        template<class RETURNTYPE>
        RETURNTYPE get(std::string& key)
        {
            // get parameter(eg. get cached value or from db...)
            const Parameter& parameter = retrieveFromDbOrCache(key);

            return <parameter.type, RETURNTYPE>getImpl(parameter);
        }

    private:
        template<int ENUMTYPE, class RETURNTYPE>
        RETURNTYPE getImpl(const Parameter& parameter)
        {
            throw "Tthe requested return type does not match with the actual parameter's type"; // shall never happen
        }

        template<Parameter::KnownTypes::STRING, std::string>
        std::string getImpl(const Parameter& parameter)
        {
            return parameter.value;
        }

        template<Parameter::KnownTypes::BOOLEAN, bool>
        std::string getImpl(const Parameter& parameter)
        {
            return parameter.value == "Y";
        }

        template<Parameter::KnownTypes::INTEGER, int>
        int getImpl(const Parameter& parameter)
        {
            return lexical_cast<int>(parameter.value)
        }

        // and so on, specialize once per known type
};

这是一个很好的实现吗?关于如何改进它的任何建议?

我知道我可以根据返回类型直接专门化 public get,但是我会在每个模板专门化中复制一些代码(类型一致性检查以及参数检索)

4

2 回答 2

2

如果您尝试实施它,您的方法将失败!问题是:

return <parameter.type, RETURNTYPE>getImpl(parameter);

或使用正确的 C++ 语法:

return getImpl<parameter.type, RETURNTYPE>(parameter);

模板参数需要是编译时常量,这parameter.type不是!所以你必须尝试这样的事情:

switch(parameter.type)
{
case STRING:
    return getImpl<STRING, RETURNTYPE>(parameter);
//...
}

看起来你根本没有得到任何东西,是吗?

不过,您可以尝试反过来,专门研究 getter 本身:

public:
    template<class RETURNTYPE>
    RETURNTYPE get(std::string const& key);

    template<>
    std::string get<std::string>(std::string const& key)
    {
        return getImpl<STRING>(key);
    }
    template<>
    int get<int>(std::string const& key)
    {
        return lexical_cast<int>(getImpl<STRING>(key));
    }

private:
    template<KnownTypes Type>
    std::string getImpl(std::string const& key)
    {
        Parameter parameter = ...;
        if(parameter.type != Type)
            throw ...;
        return parameter.value;
    }

或者没有模板(参考 Nim 的评论......):

public:
    int getInt(std::string const& key)
    {
        return lexical_cast<int>(getImpl(STRING, key));
    }

private:
    inline std::string getImpl(KnownTypes type, std::string const& key)
    {
        Parameter parameter = ...;
        if(parameter.type != type)
            throw ...;
        return parameter.value;
    }

您可能已经注意到的一个变化:我为您的参数修复了常量...

旁注:在类范围内不允许使用上述模板特化(以上是为了简短而编写的)。在您的真实代码中,您必须将专业化移出类:

struct S { template<typename T> void f(T t); };

template<> void S::f<int>(int t) { }
于 2017-05-30T12:50:29.583 回答
1

除了已接受的答案之外,我还想添加一个演示,该演示与验证类型的正确性略有不同,而无需对所有模板专业化代码进行样板化。以及更正不允许的类范围内的显式模板专业化。

class Parameter {
public:
  enum KnownTypes { STRING = 0, BOOLEAN, INTEGER, DOUBLE };

  std::string key;
  std::string value;
  KnownTypes type;
};

class Configuration {
public:
  template <class RETURNTYPE>
  RETURNTYPE get(std::string const& key) {
    // get parameter(eg. get cached value or from db...)
    std::map<std::string, Parameter> map{
      {"int", Parameter{"int", "100", Parameter::KnownTypes::INTEGER}},
      {"string", Parameter{"string", "string_value", Parameter::KnownTypes::STRING}},
      {"throwMe", Parameter{"throwMe", "throw", Parameter::KnownTypes::DOUBLE}},
      {"bool", Parameter{"bool", "Y", Parameter::KnownTypes::BOOLEAN}}};
    const Parameter& parameter = map.at(key);

    bool isMatchingType = false;
    switch (parameter.type) {
    case Parameter::STRING:
      isMatchingType = std::is_same<RETURNTYPE, std::string>::value;
      break;
    case Parameter::BOOLEAN:
      isMatchingType = std::is_same<RETURNTYPE, bool>::value;
      break;
    case Parameter::INTEGER:
      isMatchingType = std::is_same<RETURNTYPE, int>::value;
      break;
    case Parameter::DOUBLE:
      isMatchingType = std::is_same<RETURNTYPE, double>::value;
      break;
    };

    if (!isMatchingType)
      throw "Tthe requested return type does not match with the actual parameter's type";

    return getImpl<RETURNTYPE>(parameter);
  }

private:
  template <class RETURNTYPE>
  RETURNTYPE getImpl(const Parameter& parameter);
};

template <>
std::string Configuration::getImpl<std::string>(const Parameter& parameter) {
  return parameter.value;
}

template <>
bool Configuration::getImpl<bool>(const Parameter& parameter) {
  return parameter.value == "Y";
}

template <>
int Configuration::getImpl<int>(const Parameter& parameter) {
  return std::stoi(parameter.value);
}

int main() {
  Configuration conf;
  cerr << conf.get<int>("int") << endl;
  cerr << conf.get<bool>("bool") << endl;
  cerr << conf.get<string>("string") << endl;
  cerr << conf.get<string>("throwMe") << endl;

  return 0;
}
于 2017-05-30T13:13:52.227 回答