Russian Qt Forum

Программирование => С/C++ => Тема начата: ecspertiza от Октябрь 10, 2017, 18:52



Название: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 10, 2017, 18:52
Реализую у себя в коде возможность выполнения неких команд. Команда перед запуском должна быть проверена, может ли она быть запущена.

Есть вот такой пример
Код:
#include <iostream>
#include <vector>

class BaseCommand {

    public:
        enum CommandType {
            TypeA,
            TypeB
        };

        BaseCommand() = default;

        virtual void execute() = 0;
        virtual bool dublicated() = 0;
        virtual BaseCommand::CommandType commandType() = 0;
};

class CommandA : public BaseCommand {

    public:
        CommandA() = default;

        void execute() override {
            std::cout << "execure CommandA" << std::endl;
        }

        bool dublicated() override {
            return true;
        }

        BaseCommand::CommandType commandType()
        {
            return TypeA;
        }
};

class CommandB : public BaseCommand {

    public:
        CommandB() = default;

        void execute() override {
            std::cout << "execure CommandB" << std::endl;
        }

        bool dublicated() override {
            return false;
        }

        BaseCommand::CommandType commandType()
        {
            return TypeB;
        }
};

std::vector<BaseCommand *> m_arr;

bool checkCommand(BaseCommand *command)
{
    if (!command)
        return false;

    if (!command->dublicated()) {
        for (std::vector<BaseCommand *>::iterator it = m_arr.begin() ; it != m_arr.end(); ++it)
            if ((*it)->commandType() ==  command->commandType()) {
                return false;
            }
    }

    m_arr.push_back(command);

    return true;
}

int main(int argc, char *argv[])
{

    CommandA *ca = new CommandA;
    if (checkCommand(ca)) {
        ca->execute();
    }else {
        delete ca;
    }

    CommandB *cb = new CommandB;
    if (checkCommand(cb)) {
        cb->execute();
    }else {
        delete cb;
    }

    return 0;
}

Функция checkCommand просто для примера, она будет сильно расширена, и хочется что бы она работала с базовым классом. В текущем коде не нравится то, что команды создаются до того как произошла проверка на запуск. Вопрос как в данном случае можно выполнить эту проверку до того как создан экземпляр команды?
Думал сделать dublicated статичной, но статичные виртуальные функции не поддерживаются. 


Название: Re: Нетипичное наследование
Отправлено: ssoft от Октябрь 11, 2017, 07:59
Можно не создавать команду, можно проверить по типу сначала

Код:
bool checkCommand(BaseCommand::CommandType type)
{
        for (std::vector<BaseCommand *>::iterator it = m_arr.begin() ; it != m_arr.end(); ++it)
            if ((*it)->commandType() ==  type) {
                return false;
            }
    }
    return true;
}


Название: Re: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 11, 2017, 09:56
Необходимо проверить флаг dublicated, если он false то запретить создавать дубликат команды. В примере выше не понятно откуда берется переменная command.


Название: Re: Нетипичное наследование
Отправлено: ssoft от Октябрь 11, 2017, 10:27
Наверное так  ???

Код:
int main(int argc, char *argv[])
{
    if ( checkCommand( BaseCommand::TypeA ) )
         m_arr.push_back( new CommandA );
...
}


Название: Re: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 11, 2017, 10:29
Теперь нет возможности получить из потомка флаг dublicable ;-) То есть он по дефолту проверяется. А проверка должна происходить в зависимости от флага deblicable потомка.


Название: Re: Нетипичное наследование
Отправлено: ssoft от Октябрь 11, 2017, 10:38
Если dublicable является неизменяемым свойством для каждого типа команд, то можно метод оформить, как статический.

Код:
class BaseCommand
{
...
    static bool dublicated ( Type type )
    {
        switch ( type )
        {
    ...
        }
    }
};

Если же это свойство конкретного экземпляра команды (экземпляры одного класса могут быть как dublicated, так и нет), тогда уже придется создавать экземпляр.


Название: Re: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 11, 2017, 10:58
Изменяемо для каждого типа.


Название: Re: Нетипичное наследование
Отправлено: m_ax от Октябрь 11, 2017, 13:47
Изменить архитектуру, заменив функцию checkCommand на
Код
C++ (Qt)
template <class Command>
std::shared_ptr<BaseCommand> createCommand()
{
   std::shared_ptr<BaseCommand> command = std::make_shared<Command>();
   if (!command->dublicated()) {
       for (std::vector<BaseCommand *>::iterator it = m_arr.begin() ; it != m_arr.end(); ++it)
           if ((*it)->commandType() ==  command->commandType()) {
               return null_ptr;
           }
   }
 
   m_arr.push_back(command);
 
   return command;
}
 
int main(int argc, char *argv[])
{
 
   auto ca = createCommand<CommandA>();
   if (ca)
       ca->execute();
 
   auto cb = createCommand<CommandB>();
   if (cb)
       cb->execute();
 
   return 0;
}
 


Название: Re: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 11, 2017, 13:53
m_ax, если не ошибаюсь тут тоже есть создание команды, потом проверка. И в случае если проверка не прошла, command будет автоматически удален. Буду мудрить с типами и switch.


Название: Re: Нетипичное наследование
Отправлено: m_ax от Октябрь 11, 2017, 13:58
m_ax, если не ошибаюсь тут тоже есть создание команды, потом проверка. И в случае если проверка не прошла, command будет автоматически удален. Буду мудрить с типами и switch.
Конечно есть создание команды, ведь далее вызывается её метод (а как его вызвать без создания объекта?) :)


Название: Re: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 11, 2017, 14:03
Статик :-)

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


Название: Re: Нетипичное наследование
Отправлено: m_ax от Октябрь 11, 2017, 15:56
Вообще не хватает мета информации о типе, которую можно было бы проверять. Задал свойства команды в мета информацию. Проверил ее, она не изменяема и статична для каждого типа. При этом эту мета информацию можно было бы для потомков переопределять.
Ну это типичный паттерн type_traits и плюс немного меташаблонной магии от boost::variant и boost::static_visitor

Код
C++ (Qt)
#include <iostream>
#include <string>
#include <boost/variant.hpp>
#include <memory>
#include <vector>
 
struct typeA_teg{};
struct typeB_teg{};
 
 
class BaseCommand
{
public:
   typedef boost::variant<typeA_teg, typeB_teg> command_type;
 
   BaseCommand() = default;
   virtual void execute() = 0;
   virtual  command_type commandType() = 0;
};
 
class CommandA : public BaseCommand {
 
   public:
       CommandA() = default;
 
       void execute() override {
           std::cout << "execure CommandA" << std::endl;
       }
 
       BaseCommand::command_type commandType()
       {
           return typeA_teg();
       }
};
 
class CommandB : public BaseCommand {
 
   public:
       CommandB() = default;
 
       void execute() override {
           std::cout << "execure CommandB" << std::endl;
       }
 
       BaseCommand::command_type commandType()
       {
           return typeB_teg();
       }
};
 
 
template <class>
struct command_traits;
 
template <>
struct command_traits<CommandA>
{
   static constexpr bool is_dublicated = true;
   typedef typeA_teg command_type;
};
 
 
template <>
struct command_traits<CommandB>
{
   static constexpr bool is_dublicated = false;
   typedef typeB_teg command_type;
};
 
 
template <class Command>
struct is_same_command_type : public boost::static_visitor<bool>
{
   typedef typename command_traits<Command>::command_type command_type;
 
   bool operator()(command_type) const
   {
       return true;
   }
 
   template <class T>
   bool operator()(T) const
   {
       return false;
   }
};
 
std::vector<std::shared_ptr<BaseCommand>> m_arr;
 
template <class Command>
bool checkCommand()
{
 
   if (!command_traits<Command>::is_dublicated) {
       for (auto & c : m_arr)
       {
           if (boost::apply_visitor(is_same_command_type<Command>(), c->commandType())
               return false;
       }
   }
 
   m_arr.push_back(std::make_shared<Command>());
 
   return true;
}
 
Писалось на коленке, могут быть опечатки  :)


Название: Re: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 11, 2017, 16:49
Спасибо, гляну этот паттерн.


Название: Re: Нетипичное наследование
Отправлено: ViTech от Октябрь 11, 2017, 17:07
Статик :-)

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

В класс добавьте статичных констант и переопределяйте их в потомках. Правда не очень понятно, как вы это всё использовать хотите.


Название: Re: Нетипичное наследование
Отправлено: Old от Октябрь 11, 2017, 17:17
Код
C++ (Qt)
#include <iostream>
 
using namespace std;
 
template<bool isDup>
class Command
{
public:
static bool dublicated() { return isDup; }
};
 
class CommandA : public Command<true>
{
};
 
class CommandB : public Command<false>
{
};
 
int main()
{
cout << CommandA::dublicated() << endl;
cout << CommandB::dublicated() << endl;
 
return 0;
}
 


Название: Re: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 11, 2017, 19:13
Вот такие две штуки получились
Код:
#include <iostream>
#include <vector>

class BaseCommand {

    public:
        enum CommandType {
            Unknown,
            TypeA,
            TypeB
        };

        BaseCommand() = default;

        virtual void execute() = 0;
        virtual BaseCommand::CommandType commandType() = 0;
};

class CommandA : public BaseCommand {

    public:
        CommandA() = default;

        void execute() override {
            std::cout << "execure CommandA" << std::endl;
        }

        BaseCommand::CommandType commandType() override {
            return BaseCommand::TypeA;
        }
};

class CommandB : public BaseCommand {

    public:
        CommandB() = default;

        void execute() override {
            std::cout << "execure CommandB" << std::endl;
        }

        BaseCommand::CommandType commandType() override {
            return BaseCommand::TypeB;
        }
};

template <class>
struct CommandProperty;

template<>
struct CommandProperty<CommandA>
{
    static const bool is_single = false;
    static const BaseCommand::CommandType m_type = BaseCommand::TypeA;
};


template<>
struct CommandProperty<CommandB>
{
    static const bool is_single = true;
    static const BaseCommand::CommandType m_type = BaseCommand::TypeB;
};

std::vector<BaseCommand *> m_arr;

template<class Command>
bool checkCommand()
{
    bool is_single = CommandProperty<Command>::is_single;

    if (is_single) {
        for (std::vector<BaseCommand *>::iterator it = m_arr.begin() ; it != m_arr.end(); ++it)
            if ((*it)->commandType() == CommandProperty<Command>::m_type) {
                return false;
            }
    }

    return true;
}

int main(int argc, char *argv[])
{
    if (checkCommand<CommandA>())
            m_arr.push_back(new CommandA);

    if (checkCommand<CommandA>())
            m_arr.push_back(new CommandA);

    if (checkCommand<CommandB>())
            m_arr.push_back(new CommandB);

    if (checkCommand<CommandB>())
            m_arr.push_back(new CommandB);

    for (std::vector<BaseCommand *>::iterator it = m_arr.begin() ; it != m_arr.end(); ++it)
        (*it)->execute();

    return 0;
}

Код:
#include <iostream>
#include <vector>

enum CommandType {
    Unknown = 0,
    TypeA = 1,
    TypeB = 2
};

class ICommand {
public:
    virtual void execute() = 0;
    virtual CommandType commandType() = 0;
};

template<bool isSingle,
         CommandType type>
class BaseCommand : public ICommand {

    public:

        BaseCommand() = default;

        static bool is_single() {return isSingle;}
        static CommandType CommandTypeB(){return type;}
};

class CommandA : public BaseCommand<false, CommandType::TypeA> {

    public:
        CommandA() = default;

        void execute() override {
            std::cout << "execure CommandA" << std::endl;
        }

        CommandType commandType() override {
            return CommandTypeB();
        }
};

class CommandB : public BaseCommand<true, CommandType::TypeB> {

    public:
        CommandB() = default;

        void execute() override {
            std::cout << "execure CommandB" << std::endl;
        }

        CommandType commandType() override {
            return CommandTypeB();
        }
};

std::vector<ICommand *> m_arr;

template<class Command>
bool checkCommand()
{
    bool is_single = Command::is_single();

    if (is_single) {
        for (std::vector<ICommand *>::iterator it = m_arr.begin() ; it != m_arr.end(); ++it)
            if (((ICommand*)(*it))->commandType() == Command::CommandTypeB()) {
                return false;
            }
    }

    return true;
}

int main(int argc, char *argv[])
{
    if (checkCommand<CommandA>())
            m_arr.push_back(new CommandA);

    if (checkCommand<CommandA>())
            m_arr.push_back(new CommandA);

    if (checkCommand<CommandB>())
            m_arr.push_back(new CommandB);

    if (checkCommand<CommandB>())
            m_arr.push_back(new CommandB);

    for (std::vector<ICommand *>::iterator it = m_arr.begin() ; it != m_arr.end(); ++it)
        (*it)->execute();

    return 0;
}



Название: Re: Нетипичное наследование
Отправлено: ViTech от Октябрь 11, 2017, 20:43
Оффтоп. Вместо такой вереницы:
Код
C++ (Qt)
   for (std::vector<ICommand *>::iterator it = m_arr.begin() ; it != m_arr.end(); ++it)
       (*it)->execute();
можно так:
Код
C++ (Qt)
   for (ICommand * command : m_arr)
       command->execute();


Название: Re: Нетипичное наследование
Отправлено: sergek от Октябрь 11, 2017, 22:38
тогда уж так:
Код:
    for (auto command : m_arr)
            command->execute();
;)


Название: Re: Нетипичное наследование
Отправлено: Igors от Октябрь 12, 2017, 08:15
В текущем коде не нравится то, что команды создаются до того как произошла проверка на запуск. Вопрос как в данном случае можно выполнить эту проверку до того как создан экземпляр команды?
Чтобы выполнить проверку нужны те или иные данные для каждого типа команды (класса). Поэтому создавать какой-то класс все равно придется, и развивать его тоже (на виртуалах или темплейтах). Такой класс мне кажется искусственным, лучше обойтись одним классом "команда", т.е. по сути оставить как было. А проверку можно сделать в конструкторе, если не проходит - взвести флажок и быстренько выйти.
Код
C++ (Qt)
CommandA * ca = new CommandA;
if (ca->IsValid()) ...


Название: Re: Нетипичное наследование
Отправлено: Old от Октябрь 12, 2017, 08:20
А проверку можно сделать в конструкторе
Как в конструкторе команды сделать проверку, что там есть в очереди команда? Передавать в конструктор команды очередь команд? :)

взвести флажок и быстренько выйти.
Код
C++ (Qt)
CommandA * ca = new CommandA;
if (ca->IsValid()) ...
Удобно, чО. :)


Название: Re: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 12, 2017, 10:04
Передавать в конструктор команды очередь команд? :)

И да будет спагетти :-)


Название: Re: Нетипичное наследование
Отправлено: Igors от Октябрь 12, 2017, 11:24
И да будет спагетти :-)
Какое спагетти? Откуда  ??? 
Код
C++ (Qt)
   CommandA *ca = new CommandA;
   if (checkCommand(ca)) {
       ca->execute();
   }else {
       delete ca;
}
Вот я смотрю на этот кусочек и в упор не вижу - ну что же тут такого плохого? Да, создали экземпляр, он выполнил проверку - совершенно логично и ессно, он может и должен проверять, у него для этого все есть. Ну может else delete выглядит чуть неуклюже - так это легко уладить хотя бы однострочным inline. Но вместо этого мы лепим еще какую-то хренотень которая что-то решает за класс команды. Хотя тот же is_single прямо-таки просится быть членом класса команда.

И я даже знаю почему так делается :)  Хочется блеснуть владением темплейтами - и все, объективных оснований никаких


Название: Re: Нетипичное наследование
Отправлено: Old от Октябрь 12, 2017, 11:35
Вот я смотрю на этот кусочек и в упор не вижу - ну что же тут такого плохого?
Вот это и плохо. Не всегда конструктор ничего не делает.
Для чего конструировать объект, который не нужен и потом его разрушать, если достаточно статических данных?


Название: Re: Нетипичное наследование
Отправлено: ecspertiza от Октябрь 12, 2017, 11:59
Old, согласен, об этом и был основной вопрос топика.
Igors, пример который есть выше, он просто описывает проблему. В реале же, это цела структура. Создания, проверки, регистрирования команд, на выполнение. У нас есть менеджер который проверят возможность исполнения команды, при необходимости регистрирует ее и запускает на исполнение. В вашем примере, нам придется в каждую команду передать массив действий, например для того что бы проверить команды на дублирование. Получается, что каждая отдельная операция будет знать о соседних. Зачем ей это? Это уже нарушение архитектуры. Команда должна знать только о своих свойствах и действиях которые ей необходимо выполнить. Остальное должен разруливать менеджер (в примере выше это main и checkExecutable).

Шаблоны же в данном случае помогают нам решить несколько проблем
1. Конфигурирование свойств команды на этапе ее описания. Мы не ломаем ООП. Как в случае, со статичными методами для каждого дочернего класса.
2. Проверка этих свойств без создания самой команды.


Название: Re: Нетипичное наследование
Отправлено: ViTech от Октябрь 12, 2017, 12:56
тогда уж так:
Код:
    for (auto command : m_arr)
            command->execute();
;)

Хорошо, конечно, всё на компилятор скинуть, но это может привести к тому, что разработчики думать совсем перестанут. Тогда уж так:
Код:
    for (auto & command : m_arr)
            command->execute();
;)

Но, на мой взгляд, читабельность страдает. Всё хорошо в меру :).


Название: Re: Нетипичное наследование
Отправлено: Авварон от Октябрь 12, 2017, 17:05
auto&& тогда уж и не думать)


Название: Re: Нетипичное наследование
Отправлено: Igors от Октябрь 12, 2017, 17:32
Шаблоны же в данном случае помогают нам решить несколько проблем..
Простой пример с is_single можно делать как угодно, напр мапой. Вопрос в дальнейшей расширяемости, гибкости и удобстве. И тут, полагаю, Вас привлекло это
Код
C++ (Qt)
template<class Command>
bool checkCommand()
{
...
}
 
Мол, если возникнет какая-то специфика для др команды - мы специализируем для нее шаблон, и все дела! (поправьте если не так понял). Ну то смотря как (или куда) проверки разрастутся. У checkCommand должны быть данные команды чтобы он мог что-то решать. А как их получить если экземпляра нет? На одних статиках далеко не уехать.

В вашем примере, нам придется в каждую команду передать массив действий, например для того что бы проверить команды на дублирование. Получается, что каждая отдельная операция будет знать о соседних. Зачем ей это?
С этим согласен. Но можно без затей создать экземпляр и подать его менеджеру - пусть он решает. Не вижу зачем (упорно) избегать создания экземпляра - это уже обошлось недешево, код заметно усложнился.


Название: Re: Нетипичное наследование
Отправлено: m_ax от Октябрь 12, 2017, 20:47
Цитировать
auto&& тогда уж и не думать)
А чем это лучше auto & ?
На сколько я понимаю, (возможно я не прав) это вызовет копирующий конструктор..

Цитировать
Мол, если возникнет какая-то специфика для др команды - мы специализируем для нее шаблон, и все дела!
Да, именно так. Это тот самый случай, когда необходимо разруливать (развлетвлять) поведение системы в зависимости только от пренадлежности её к определённому типу, минуя необходимость создавать объект класса.. Это классический паттерн класс харрактеристик (type traits). И да, он работает (и как это видно из контекста) в тех случаях, когда  "checkCommand не обязан иметь внутренние данные команды чтобы он мог что-то решать."  :) Короче, всё решается в зависимости только от типа.


Название: Re: Нетипичное наследование
Отправлено: Old от Октябрь 12, 2017, 20:54
Короче, всё решается в зависимости только от типа.
Скукатень. А где здесь развесистые switch'и? :)


Название: Re: Нетипичное наследование
Отправлено: sergek от Октябрь 12, 2017, 20:55
А чем это лучше auto & ?
При чем тут лучше/хуже? Если нет цели изменить содержимое m_arr, зачем нужно создавать ссылку на элемент контейнера? Так что в данном случае просто auto будет достаточно.

ps. прошу прощения за офтоп.


Название: Re: Нетипичное наследование
Отправлено: m_ax от Октябрь 12, 2017, 21:04
Цитировать
Так что в данном случае просто auto будет достаточно.
Нет, не всегда.. Если нет копирующего конструктора, определённого пользователем, то будет вызван конструктор по умолчанию (поправьте, если не так), что может быть затратно..

Цитировать
Скукатень. А где здесь развесистые switch'и?  :)
Это да - не канонично  ;D


Название: Re: Нетипичное наследование
Отправлено: Авварон от Октябрь 12, 2017, 23:08
auto&& - это т.н. "универсальная ссылка", которая биндится либо к T&, либо к T&& в зависимости от того, что стоит справа от = (т.е. в случае цикла, что возвращает operator* итератора).
Основной юзкейз использования - если предполагается делать std::forward.

Т.е., например, можно сделать итератор, который будет мувать элементы из контейнера, если контейнер - временный объект (перегрузить begin()&& и end)&&).
На практике такое, конечно же, не всречается, даже разрабы стандарта не столь упороты.

Минусом у auto&& является то, что она теряет константность, если контейнер в for-е неконстантный, а итератор возвращает ссылку.
Самое смешное, что тут поможет qAsConst, который сделан, чтобы залечить проблемы implicit sharing.


Название: Re: Нетипичное наследование
Отправлено: Igors от Октябрь 13, 2017, 12:04
Да, именно так. Это тот самый случай, когда необходимо разруливать (развлетвлять) поведение системы в зависимости только от пренадлежности её к определённому типу, минуя необходимость создавать объект класса.. Это классический паттерн класс харрактеристик (type traits). И да, он работает (и как это видно из контекста) в тех случаях, когда  "checkCommand не обязан иметь внутренние данные команды чтобы он мог что-то решать."  :) Короче, всё решается в зависимости только от типа.
Очень сомневаюсь что на голом типе/статиках можно много решить

Нет, не всегда.. Если нет копирующего конструктора, определённого пользователем, то будет вызван конструктор по умолчанию (поправьте, если не так), что может быть затратно..
Конструктор копирования по умолчанию, который автоматычно вызывает конструкторы копирования для всех членов.

А все-таки как охотно подхватывается любой "синтаксический сахар"  :) До самой задачи дела никому нет, главное - чтобы написано было круто. А еще говорят что погоня за модой - чисто женская черта  :)


Название: Re: Нетипичное наследование
Отправлено: Old от Октябрь 13, 2017, 12:09
А все-таки как охотно подхватывается любой "синтаксический сахар"  :) До самой задачи дела никому нет, главное - чтобы написано было круто. А еще говорят что погоня за модой - чисто женская черта  :)
:)
Как раз задачу мы и решали, в отличие от вас. :)
Мы добились того, что необходимые флаги для каждой команды можно описывать при создании новой команды и проверять их без создания экземпляра команды.
Если в дальнейшем, для принятия решения, понадобится экземпляр класса задачи, его всегда можно создать.