Идеи по избавлению от кода котельной плиты - PullRequest
1 голос
/ 24 июня 2019

Моя проблема очень специфическая.У меня есть следующее требование, мне нужно установить переменные-члены, которые существуют в дочернем классе из родительского класса, по нескольким причинам.Мой план состоит в том, чтобы передать указатель функции метода setter (который существует в дочернем классе), который принимает строку в качестве аргумента, родительскому классу при создании.Родительский класс определяет открытый метод, который принимает имя члена и строку значения в качестве аргумента и вызывает указатель функции члена со строкой значения.Родительский класс может существовать в dll или lib и не иметь доступа ни к каким методам преобразования или фабрики, поэтому метод определения должен быть определен в дочернем классе.

Поскольку родительский класс может быть базовым классом для других классовЯ написал несколько макросов, показанных ниже:

#define DEFINE_VAL(Type, memberName) \
        private: \
            Type memberName; \
            void set##memberName(std::string const& val) { \
                memberName = convert_to_val(val); /* this will be a call to factory which converts string to value type*/\
/* or call to local implementation for conversion*/
            }; \

#define INIT_VAL(memberName) \
            { memberName, \
            [&](std::string const& val) { set##memberName(val); }}

Родительский и дочерний классы такие, как показано ниже:

// parent.h probably in dll
class parent
{
public:
    parent(std::map<std::string, std::function<void(std::string const&)>>& m)
        : m(m)
    { }
        ... 
private:
    std::map<std::string, std::function<void(std::string const&)>> m;
};

// child.h
class child : public parent
{
public:
    child() : parent({ INIT_VAL(iVal), ... })
    { }
private:
    DEFINE_VAL(int, iVal);
        ...
};

Дочерний класс может иметь много определенных переменных, и его немного раздражает первыйиспользуйте макрос DEFINE_VAL, а затем передайте метод установки каждой переменной с помощью макроса INIT_VAL.Можно ли это сделать в одном макросе (возможно, в DEFINE_VAL)?или какие-либо идеи по автоматической регистрации имени члена и указателя на функцию родительского класса?

Буду также признателен за любые альтернативные идеи по выполнению моего требования.

Ответы [ 2 ]

2 голосов
/ 24 июня 2019

Мне нужно установить переменные-члены, которые существуют в дочернем классе из родительского класса, по нескольким причинам. Мой план состоит в том, чтобы передать указатель функции метода setter (который существует в дочернем классе), который принимает строку в качестве аргумента в родительский класс при построении .

Когда вызывается конструктор родительского класса, производный класс и его члены еще не инициализированы и, педантично, они еще не существуют. По этой причине невозможно установить члены производного класса из его конструктора базового класса.


Одним из решений является использование виртуальной функции для задания членов по имени.

Без встроенного отражения в текущем C ++, чтобы связать имена с элементами данных и генерировать методы доступа к элементам, лучше всего по-прежнему использовать макросы. Один из лучших макросов для этой цели - BOOST_HANA_DEFINE_STRUCT.

boost::lexical_cast<T> можно использовать для преобразования из std::string в любой T.

Рабочий пример с поддержкой глубокого и множественного наследования:

#include <boost/hana/define_struct.hpp>
#include <boost/hana/accessors.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/concat.hpp>
#include <boost/hana/length.hpp>

#include <boost/lexical_cast.hpp>

#include <unordered_map>
#include <functional>
#include <iostream>

namespace hana = boost::hana;

struct MemberSetter {
    // Using void* to reduce the number of template instantiations.
    using SetterFn = std::function<void(void*, std::string const&)>;
    using Setters = std::unordered_map<std::string, SetterFn>;

    Setters setters_;

    template<class Derived, class Accessors>
    MemberSetter(Derived* that, Accessors& accessors) {
        hana::for_each(accessors, [this](auto const& pair) {
            auto setter = [accessor = hana::second(pair)](void* vthat, std::string const& value) {
                auto* that = static_cast<Derived*>(vthat);
                auto& member = accessor(*that);
                member = boost::lexical_cast<std::remove_reference_t<decltype(member)>>(value);
            };
            auto name = hana::first(pair);
            setters_.emplace(std::string(hana::to<char const*>(name), hana::length(name)), std::move(setter));
        });
    }

    bool findAndSetMember(void* that, std::string const& name, std::string const& value) const {
        auto setter = setters_.find(name);
        if(setter != setters_.end()) {
            (setter->second)(that, value);
            return true;
        }
        return false;
    }
};

struct A {
    virtual ~A() = default;
    virtual bool setMember(std::string const& name, std::string const& value) = 0;
};

struct B : A {
    BOOST_HANA_DEFINE_STRUCT(B,
        (int, a),
        (double, b)
        );

    bool setMember(std::string const& name, std::string const& value) override {
        constexpr auto accessors = hana::accessors<B>();
        static MemberSetter const setter(this, accessors);
        return setter.findAndSetMember(this, name, value);
    }
};

struct C : B {
    BOOST_HANA_DEFINE_STRUCT(C,
        (std::string, c)
        );

    bool setMember(std::string const& name, std::string const& value) override {
        constexpr auto accessors = hana::concat(hana::accessors<B>(), hana::accessors<C>()); // Join with members of the base class.
        static MemberSetter const setter(this, accessors);
        return setter.findAndSetMember(this, name, value);
    }
};

int main() {
    C c;
    c.setMember("a", "1");
    c.setMember("b", "2.3");
    c.setMember("c", "hello");
    std::cout << c.a << ' ' << c.b << ' ' << c.c << '\n';
}

Вывод:

1 2.3 hello
1 голос
/ 24 июня 2019

Просто используйте виртуальную функцию, чтобы установить ее, и переместите карту в дочернюю, поскольку это действительно должно быть деталью реализации. Таким образом, родительский класс на самом деле не имеет ничего общего с тем, как установлены члены.

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)
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...