简体   繁体   中英

How can I make this more robust, given that there is no reflection in C++?

I have the following class:

template <typename T>
T get_value(std::string name, T defaultValue)
{
  // read values from config file and assign to var; if not found use defaultValue
  return defaultValue;
}

class A {
public:
  A()
    : myvar1_(get_value("myvar1", 0))
    , myvar2_(get_value("myvar2", 1.5))
  {
  }

  int myvar1_;
  double myvar2_;

  std::string asString() const {
    std::stringstream str;
    str << "myvar1 = " << myvar1_
        << ", myvar2 = " << myvar2_;
    return str.str();
  }
private:
  // other things exist here, unrelated to myvar1/2
};

Where get_value is a function that reads the values of the variables from some config file, and if not found uses the default value. I also have myvar1_ and myvar2_ as public member variables, because they get accessed directly, and I'd like to keep that feature and do not want to add a potential function hop.

Now, you can see that I have typed myvar1 or myvar1_ in quite a few different places, and I'd like to make this more robust, so that I can type, somewhere somehow, myvar1_, "myvar1", 0 once (as opposed to having typed myvar1_ 3 times and "myvar1" twice), and automagically get the above functions called and values filled. I have a lot of variables and they get added or removed fairly frequently, and sometimes I forget to initialize them, or mistype the string name in set_value , or forget to add a new variable to asString .

Is that possible to do? I'd appreciate any hints.

Option 1: A DSL that generates your code from definitions.

Option 2: Use a class facade over the config lookup, backed presumably by access to your config class, and combine with C++11's member initialization. This will reduce the number of times you have to repeat a variable, which can then be hidden with a little macroing if you're up for that.

#include <iostream>

template<typename T>
class DV {
    T value_;
public:
    DV(const char* name, const T& defaultValue)
    {
        // read values from config file and assign to var; if not found use defaultValue
        value_ = defaultValue;
    }

    operator const T& () const { return value_; }
};

using DVInt = DV<int>;
using DVStr = DV<const char*>;

struct A {
    int i_ = DVInt("i", 42);
    const char* str_ = DVStr("str", "hello");

    A() = default;
};

int main() {
    A a;
    std::cout << a.i_ << ", " << a.str_ << "\n";
}

Demo: http://ideone.com/RAyKwI

-- Edit --

Reducing to one instance with a macro.

Instead of the using statements:

#include <iostream>

template<typename T>
class DV {
    T value_;
public:
    DV(const char* name, const T& defaultValue)
    {
        // read values from config file and assign to var; if not found use defaultValue
        value_ = defaultValue;
    }

    operator const T& () const { return value_; }
};

#define CONCAT(x, y) x ## y
#define DVDecl(T, name, def) T CONCAT(name, _) = DV<T>(#name, def)
#define DVInt(name, def) DVDecl(int, name, def)
#define DVCStr(name, def) DVDecl(const char*, name, def)

struct A {
    DVInt(myvar1, 42);
    DVCStr(myvar2, "hello");

    A() = default;
};

int main() {
    A a;
    std::cout << a.myvar1_ << ", " << a.myvar2_ << "\n";
}

http://ideone.com/JmgfH9

This doesn't eliminate your need to manually add them to asString , though.

I ended up with a macro solution, heavily borrowing from a different SO answer:

#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0 (EVAL0 (EVAL0 (__VA_ARGS__)))
#define EVAL2(...) EVAL1 (EVAL1 (EVAL1 (__VA_ARGS__)))
#define EVAL3(...) EVAL2 (EVAL2 (EVAL2 (__VA_ARGS__)))
#define EVAL4(...) EVAL3 (EVAL3 (EVAL3 (__VA_ARGS__)))
#define EVAL(...)  EVAL4 (EVAL4 (EVAL4 (__VA_ARGS__)))

#define MAP_END(...)
#define MAP_OUT

#define MAP_GET_END0() 0, MAP_END
#define MAP_GET_END1(...) 0

#define GET_MACRO(_0, _1, _2, _3, _4, NAME, ...) NAME
#define MAP_GET_END(...) GET_MACRO(_0, ##__VA_ARGS__, MAP_GET_END1, MAP_GET_END1, MAP_GET_END1, MAP_GET_END1, MAP_GET_END0)(__VA_ARGS__)

#define MAP_NEXT0(item, next, ...) next MAP_OUT
#define MAP_NEXT1(item, next) MAP_NEXT0 (item, next, 0)
#define MAP_NEXT(item, next)  MAP_NEXT1 (MAP_GET_END item, next)

#define MAP0(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP1) (f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP0) (f, peek, __VA_ARGS__)

#define MAP(f, ...) EVAL (MAP1 (f, __VA_ARGS__, (), 0))


#define DEFINE_VARS_T(TYPE, NAME, DEFAULT_VALUE) \
  TYPE NAME##_;
#define DEFINE_VARS(TUPLE) DEFINE_VARS_T TUPLE

#define CONSTRUCT_VARS_T(TYPE, NAME, DEFAULT_VALUE) \
  NAME##_ = get_value(#NAME, DEFAULT_VALUE);
#define CONSTRUCT_VARS(TUPLE) CONSTRUCT_VARS_T TUPLE

#define PRINT_VARS_T(TYPE, NAME, DEFAULT_VALUE) \
  << #NAME << " = " << NAME##_ << ", "
#define PRINT_VARS(TUPLE) PRINT_VARS_T TUPLE

#define CONFIG_VARS(...) \
  MAP(DEFINE_VARS, __VA_ARGS__) \
  A() { \
    MAP(CONSTRUCT_VARS, __VA_ARGS__) \
  } \
  std::string asString() const { \
    std::stringstream str; \
    str MAP(PRINT_VARS, __VA_ARGS__); \
    return str.str(); \
  }

template <typename T>
T get_value(std::string name, T defaultValue)
{
  // read values from config file and assign to var
  return defaultValue;
}

class A {
public:
  CONFIG_VARS
  (
    (int, myvar1, 0),
    (double, myvar2, 1.5),
    (std::string, myvar3, "what")
  )

private:
  // other things exist here, unrelated to myvar1/2
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM