LINUX.ORG.RU

История изменений

Исправление KivApple, (текущая версия) :

Я привёл слишком абстрактный пример. Сейчас попробую привести пример конкретнее.

Допустим, я реализую паттерн «стратегия» - есть некий набор алгоритмов с общим интерфейсом и я хочу иметь возможность в другом коде дёргать эти алгоритмы с runtime полиморфизмом. Но это ещё не всё - алгоритмы не простые, а многоэтапные и у алгоритма есть состояние.

Условно:

struct AbstractAlgorithmState {
    AbstractAlgorithm &algorithm;
};

class AbstractAlgorithm {
public:
    virtual AbstractAlgorithmState *createState() = 0;
    virtual void updateState(AbstractAlgorithmState *state) = 0;
    virtual void deleteState(AbstractAlgorithmState *state) = 0;
};

struct MyAlgorithmState: public AbstractAlgorithmState {
    int a;
}

class MyAlgorithm: public AbstractAlgorithm {
public:
    AbstractAlgorithmState *createState() override {
        return new MyAlgorithmState { *this, 0 };
    }
    void updateState(AbstractAlgorithmState *state) override {
        auto myState = (MyAlgorithmState*) state;
        myState->a++;
        std::cout << "A = " << myState->a << std::endl;
    }
    void deleteState(AbstractAlgorithmState *state) override {
        delete (MyAlgorithmState*) state; // Вдруг MyAlgorithmState содержит поля с нетривиальными деструктурами вроде vector или string
    }
}

...

AbstractAlgorithmState *state = findAlgorithmByName(...)->createState();
state->algoritm.updateState(state);
state->algoritm.updateState(state);
state->algoritm.updateState(state);
state->algoritm.deleteState(state);

Наследников у AbstractAlgorithm может быть много, методов вроде updateState может быть много. У наследников AbstractAlgorithm могут быть свои поля «глобального состояния», а не всё их состояние хранится в AbstractAlgorithmState. Полиморфизм обязательно нужен в runtime.

Я согласен на любую шаблонную магию, но хочу чтобы написание наследников AbstractAlgorithm было максимально простым и приятным. Требование явного каста всё портит. Хочется, чтобы тип для хранения состояния прописывался в одном месте и больше касты были не нужны (и тогда, например, нельзя выстрелить себе в ногу и создать состояние одного типа, а в функциях обновления работать с другим типом).

Можно было бы вынести обновление состояния в само состояние, но это имеет минус в том, что тогда состояние потолстеет - помимо указателя на экземпляр AbstractAlgorithm (ведь часть состояния, напомню, «глобальная») он будет ещё содержать указатель на vtable. А экземпляров состояний одновременно могут существовать сотни тысяч (при этом у каждого алгоритма состояние очень мало весит - считанные байты) и хочется поэкономить память.

Вариант с шаблонными костылями как я описал в стартовом посте (переопределять в шаблоне виртуальный метод и вызывать другой виртуальный метод, но уже с кастом) приводит к двойному косвенному переходу вместо одного (потеря скорости, а updateState будет вызываться очень-очень много раз).

Исправление KivApple, :

Я привёл слишком абстрактный пример. Сейчас попробую привести пример конкретнее.

Допустим, я реализую паттерн «стратегия» - есть некий набор алгоритмов с общим интерфейсом и я хочу иметь возможность в другом коде дёргать эти алгоритмы с runtime полиморфизмом. Но это ещё не всё - алгоритмы не простые, а многоэтапные и у алгоритма есть состояние.

Условно:

struct AbstractAlgorithmState {
    AbstractAlgorithm &algorithm;
};

class AbstractAlgorithm {
public:
    virtual AbstractAlgorithmState *createState() = 0;
    virtual void updateState(AbstractAlgorithmState *state) = 0;
    virtual void deleteState(AbstractAlgorithmState *state) = 0;
};

struct MyAlgorithmState: public AbstractAlgorithmState {
    int a;
}

class MyAlgorithm: public AbstractAlgorithm {
public:
    AbstractAlgorithmState *createState() override {
        return new MyAlgorithmState { *this, 0 };
    }
    void updateState(AbstractAlgorithmState *state) override {
        auto myState = (MyAlgorithmState*) state;
        myState->a++;
        std::cout << "A = " << myState->a << std::endl;
    }
    void deleteState(AbstractAlgorithmState *state) override {
        delete (MyAlgorithmState*) state; // Вдруг MyAlgorithmState содержит поля с нетривиальными деструктурами вроде vector или string
    }
}

...

AbstractAlgorithmState *state = findAlgorithmByName(...)->createState();
state->algoritm.updateState();
state->algoritm.updateState();
state->algoritm.updateState();
state->algoritm.deleteState(state);

Наследников у AbstractAlgorithm может быть много, методов вроде updateState может быть много. У наследников AbstractAlgorithm могут быть свои поля «глобального состояния», а не всё их состояние хранится в AbstractAlgorithmState. Полиморфизм обязательно нужен в runtime.

Я согласен на любую шаблонную магию, но хочу чтобы написание наследников AbstractAlgorithm было максимально простым и приятным. Требование явного каста всё портит. Хочется, чтобы тип для хранения состояния прописывался в одном месте и больше касты были не нужны (и тогда, например, нельзя выстрелить себе в ногу и создать состояние одного типа, а в функциях обновления работать с другим типом).

Можно было бы вынести обновление состояния в само состояние, но это имеет минус в том, что тогда состояние потолстеет - помимо указателя на экземпляр AbstractAlgorithm (ведь часть состояния, напомню, «глобальная») он будет ещё содержать указатель на vtable. А экземпляров состояний одновременно могут существовать сотни тысяч (при этом у каждого алгоритма состояние очень мало весит - считанные байты) и хочется поэкономить память.

Вариант с шаблонными костылями как я описал в стартовом посте (переопределять в шаблоне виртуальный метод и вызывать другой виртуальный метод, но уже с кастом) приводит к двойному косвенному переходу вместо одного (потеря скорости, а updateState будет вызываться очень-очень много раз).

Исправление KivApple, :

Я привёл слишком абстрактный пример. Сейчас попробую привести пример конкретнее.

Допустим, я реализую паттерн «стратегия» - есть некий набор алгоритмов с общим интерфейсом и я хочу иметь возможность в другом коде дёргать эти алгоритмы с runtime полиморфизмом. Но это ещё не всё - алгоритмы не простые, а многоэтапные и у алгоритма есть состояние.

Условно:

struct AbstractAlgorithmState {
    AbstractAlgorithm &algorithm;
};

class AbstractAlgorithm {
public:
    virtual AbstractAlgorithmState *createState() = 0;
    virtual void updateState(AbstractAlgorithmState *state) = 0;
    virtual void deleteState(AbstractAlgorithmState *state) = 0;
};

struct MyAlgorithmState: public AbstractAlgorithmState {
    int a;
}

class MyAlgorithm: public AbstractAlgorithmState {
public:
    AbstractAlgorithmState *createState() override {
        return new MyAlgorithmState { *this, 0 };
    }
    void updateState(AbstractAlgorithmState *state) override {
        auto myState = (MyAlgorithmState*) state;
        myState->a++;
        std::cout << "A = " << myState->a << std::endl;
    }
    void deleteState(AbstractAlgorithmState *state) override {
        delete (MyAlgorithmState*) state; // Вдруг MyAlgorithmState содержит поля с нетривиальными деструктурами вроде vector или string
    }
}

...

AbstractAlgorithmState *state = findAlgorithmByName(...)->createState();
state->algoritm.updateState();
state->algoritm.updateState();
state->algoritm.updateState();
state->algoritm.deleteState(state);

Наследников у AbstractAlgorithm может быть много, методов вроде updateState может быть много. У наследников AbstractAlgorithm могут быть свои поля «глобального состояния», а не всё их состояние хранится в AbstractAlgorithmState. Полиморфизм обязательно нужен в runtime.

Я согласен на любую шаблонную магию, но хочу чтобы написание наследников AbstractAlgorithm было максимально простым и приятным. Требование явного каста всё портит. Хочется, чтобы тип для хранения состояния прописывался в одном месте и больше касты были не нужны (и тогда, например, нельзя выстрелить себе в ногу и создать состояние одного типа, а в функциях обновления работать с другим типом).

Можно было бы вынести обновление состояния в само состояние, но это имеет минус в том, что тогда состояние потолстеет - помимо указателя на экземпляр AbstractAlgorithm (ведь часть состояния, напомню, «глобальная») он будет ещё содержать указатель на vtable. А экземпляров состояний одновременно могут существовать сотни тысяч (при этом у каждого алгоритма состояние очень мало весит - считанные байты) и хочется поэкономить память.

Вариант с шаблонными костылями как я описал в стартовом посте (переопределять в шаблоне виртуальный метод и вызывать другой виртуальный метод, но уже с кастом) приводит к двойному косвенному переходу вместо одного (потеря скорости, а updateState будет вызываться очень-очень много раз).

Исходная версия KivApple, :

Я привёл слишком абстрактный пример. Сейчас попробую привести пример конкретнее.

Допустим, я реализую паттерн «стратегия» - есть некий набор алгоритмов с общим интерфейсом и я хочу иметь возможность в другом коде дёргать эти алгоритмы с runtime полиморфизмом. Но это ещё не всё - алгоритмы не простые, а многоэтапные и у алгоритма есть состояние.

Условно:

struct AbstractAlgorithmState {
    AbstractAlgorithm &algorithm;
};

class AbstractAlgorithm {
public:
    virtual AbstractAlgorithmState *createState() = 0;
    virtual void updateState(AbstractAlgorithmState *state) = 0;
    virtual void deleteState(AbstractAlgorithmState *state) = 0;
};

struct MyAlgorithmState: public AbstractAlgorithmState {
    int a;
}

class MyAlgorithm: public AbstractAlgorithmState {
public:
    AbstractAlgorithmState *createState() override {
        return new MyAlgorithmState { *this, 0 };
    }
    void updateState(AbstractAlgorithmState *state) {
        auto myState = (MyAlgorithmState*) state;
        myState->a++;
        std::cout << "A = " << myState->a << std::endl;
    }
    void deleteState(AbstractAlgorithmState *state) override {
        delete (MyAlgorithmState*) state; // Вдруг MyAlgorithmState содержит поля с нетривиальными деструктурами вроде vector или string
    }
}

...

AbstractAlgorithmState *state = findAlgorithmByName(...)->createState();
state->algoritm.updateState();
state->algoritm.updateState();
state->algoritm.updateState();
state->algoritm.deleteState(state);

Наследников у AbstractAlgorithm может быть много, методов вроде updateState может быть много. У наследников AbstractAlgorithm могут быть свои поля «глобального состояния», а не всё их состояние хранится в AbstractAlgorithmState. Полиморфизм обязательно нужен в runtime.

Я согласен на любую шаблонную магию, но хочу чтобы написание наследников AbstractAlgorithm было максимально простым и приятным. Требование явного каста всё портит. Хочется, чтобы тип для хранения состояния прописывался в одном месте и больше касты были не нужны (и тогда, например, нельзя выстрелить себе в ногу и создать состояние одного типа, а в функциях обновления работать с другим типом).

Можно было бы вынести обновление состояния в само состояние, но это имеет минус в том, что тогда состояние потолстеет - помимо указателя на экземпляр AbstractAlgorithm (ведь часть состояния, напомню, «глобальная») он будет ещё содержать указатель на vtable. А экземпляров состояний одновременно могут существовать сотни тысяч (при этом у каждого алгоритма состояние очень мало весит - считанные байты) и хочется поэкономить память.

Вариант с шаблонными костылями как я описал в стартовом посте (переопределять в шаблоне виртуальный метод и вызывать другой виртуальный метод, но уже с кастом) приводит к двойному косвенному переходу вместо одного (потеря скорости, а updateState будет вызываться очень-очень много раз).