Просто используйте виртуальную функцию, чтобы установить ее, и переместите карту в дочернюю, поскольку это действительно должно быть деталью реализации. Таким образом, родительский класс на самом деле не имеет ничего общего с тем, как установлены члены.
class parent
{
public:
virtual ~parent() = default;
protected:
virtual void do_set(const std::string& name, const std::string& value) = 0;
private:
void set(const std::string& name, const std::string& value) {
do_set(name, value);
// Do synchronization here
}
};
class child : public parent
{
protected:
void do_set(const std::string& name, const std::string& value) override {
child::setter_map.at(name)(*this, value);
}
private:
int iVal;
static const std::map<std::string, void(*)(child&, const std::string&)> setter_map;
};
#define INIT_VAL(NAME, ...) { #NAME, [](child& c, const std::string& value) __VA_ARGS__ }
const std::map<std::string, void(*)(child&, const std::string&)> child::setter_map = {
INIT_VAL(iVal, {
c.iVal = convert_to_val(value);
}),
// Init other members
};
И из этого вы можете найти лучший способ реализации set
(может быть, простой if (name == ...) ... else if (name == ...) ...
будет работать)
Или, если вы не хотите использовать полиморфизм во время выполнения, по крайней мере, не храните карту в каждом экземпляре parent
. Сохраните ссылку на глобальную карту (которая была бы похожа на сам vtable):
class parent
{
public:
parent() = delete;
protected:
using setter_map = std::map<std::string, void(*)(parent&, const std::string&)>;
parent(const setter_map& child_smap) noexcept : smap(&child_smap) {};
private:
void set(const std::string& name, const std::string& value) {
smap->at(name)(*this, value);
// Do synchronization here
}
const setter_map* smap;
};
class child : public parent {
public:
child() : parent(smap) {};
private:
int iVal;
static const setter_map smap;
};
#define INIT_VAL(NAME, ...) { #NAME, \
[](parent& _c, const std::string& value) { \
child& c = static_cast<child&>(_c); \
__VA_ARGS__ \
} \
}
const child::setter_map child::smap = {
INIT_VAL(iVal, {
c.iVal = convert_to_val(value);
}),
// (Other member setters here)
};
#undef INIT_VAL
// Or having the setters inside the class, like in your original code
class child2 : public parent {
public:
child2() : parent(smap) {};
private:
int iVal;
void set_iVal(const std::string& value) {
iVal = convert_to_val(value);
}
// Using a macro (Probably don't need the macros here, writing out a setter is more clear)
template<class T>
using type = T;
#define DEFINE_VAL(TYPE, NAME, ...) \
void set_ ## NAME (const std::string& value) { \
__VA_ARGS__ \
} \
type<TYPE> NAME
DEFINE_VAL(float, fVal, {
fVal = convert_val_to_float(value);
});
DEFINE_VAL(char[2], charArrVal, {
charArrVal[0] = value[0];
charArrVal[1] = value[1];
});
static const setter_map smap;
};
#define INIT_VAL(NAME) { #NAME, [](parent& p, const std::string& value) { static_cast<child2&>(p).set_ ## NAME (value); } }
const child2::setter_map child2::smap = {
INIT_VAL(iVal), INIT_VAL(fVal), INIT_VAL(charArrVal)
};
#undef INIT_VAL
// Or if `convert_to_val(value)` is literally the body of every setter, that simplifies the `INIT_VAL` macro
class child3 : public parent {
public:
child3() : parent(smap) {};
private:
int iVal;
static const setter_map smap;
};
#define INIT_VAL(NAME) { #NAME, [](parent& p, const std::string& value) { static_cast<child3&>(p). NAME = convert_to_val(value); } }
const child3::setter_map child3::smap = {
INIT_VAL(iVal)
};