Хранение в контейнере объектов разного типа
От: theCreature  
Дата: 23.11.11 19:43
Оценка:
Имеется класс команда и куча производных от него конкретных команд:
class Command {
public:
    enum Type {
        Command1,
        Command2,
        etc..
    };

    Command(Type type) : type_(type) { }
    virtual ~Command() = 0;

    Type type() const { return type_; }

private:
    Type type_;
};


class ConcreteCommand1 : public Command {
public:
    ConcreteCommand1(int argument) : Command(Command1), argument_(argument) { }

    int argument() const { return argument_; }

private:
    int arguement_;
};

Имеется класс, который должен содержать очередь таких команд, у которого есть метод добавления команды в очередь:
class CommandExecutor {
public:
    void enqueueCommand(const Command* command);

private:
    std::queue<Command*> inputQueue_;
};



Т.к. классов команд несколько, приходится хранить в очереди не сами объекты, а указатели на них. CommandExecutor после выборки команды из очереди и ее исполнения просто уничтожает соответствующий объект. Получается, что для передачи команды в CommandExecutor::enqueueCommand необходимо создать объект конкретной команды в куче. Собственно ничего не мешает это сделать, но хочется писать код, который сложно использовать неправильно. В данном случае придется откомментировать enqueueCommand, обозначив необходимость создания объектов Command в куче и переход права владения на объект к CommandExecutor. Ну а теперь собственно сам вопрос: как лучше решается такая проблема? Исключительно комментированием условий использования или все же можно сделать реализацию поизящнее?
Мне на ум приходит разве что использование pimpl для Command. Но в этом случае придется создавать кучу дополнительного кода для каждого конкретного класса-команды. Так же можно воспользоваться boost::any, но очень не хочется добавлять дополнительные накладные расходы, т.к. код довольно критичен ко времени выполнения.
Re: Хранение в контейнере объектов разного типа
От: Ytz https://github.com/mtrempoltsev
Дата: 24.11.11 03:48
Оценка: 1 (1)
Здравствуйте, theCreature, Вы писали:

C>Т.к. классов команд несколько, приходится хранить в очереди не сами объекты, а указатели на них. CommandExecutor после выборки команды из очереди и ее исполнения просто уничтожает соответствующий объект. Ну а теперь собственно сам вопрос: как лучше решается такая проблема? Исключительно комментированием условий использования или все же можно сделать реализацию поизящнее?


Я обычно методы принимающие владение оформляю так:

void TakeOwnership(std::auto_ptr<T> object); // Принимает владение


Эстафетное владение auto_ptr здесь очень кстати.
Re: Хранение в контейнере объектов разного типа
От: 13akaEagle Россия  
Дата: 24.11.11 03:51
Оценка: 1 (1)
Здравствуйте, theCreature, Вы писали:

C>Т.к. классов команд несколько, приходится хранить в очереди не сами объекты, а указатели на них. CommandExecutor после выборки команды из очереди и ее исполнения просто уничтожает соответствующий объект. Получается, что для передачи команды в CommandExecutor::enqueueCommand необходимо создать объект конкретной команды в куче. Собственно ничего не мешает это сделать, но хочется писать код, который сложно использовать неправильно. В данном случае придется откомментировать enqueueCommand, обозначив необходимость создания объектов Command в куче и переход права владения на объект к CommandExecutor. Ну а теперь собственно сам вопрос: как лучше решается такая проблема? Исключительно комментированием условий использования или все же можно сделать реализацию поизящнее?

C>Мне на ум приходит разве что использование pimpl для Command. Но в этом случае придется создавать кучу дополнительного кода для каждого конкретного класса-команды. Так же можно воспользоваться boost::any, но очень не хочется добавлять дополнительные накладные расходы, т.к. код довольно критичен ко времени выполнения.

Так для полиморфного использования в любом случае придётся пользоваться указаелями, но наверное лучше использовать unique_ptr из нового стандарта. Что будет говорить, что объект имеет политику одиночного владения.
А Command разрешть создавать только фабрике, которая будет отдавать unique_ptr<Command>.
Re: Хранение в контейнере объектов разного типа
От: _niko_ Россия  
Дата: 24.11.11 05:21
Оценка: 1 (1) -1
Здравствуйте, theCreature, Вы писали:

C> . . .


Для начала:
virtual ~Command() = 0;

— деструктор абстрактным быть не может.


Если не хотите иметь дело с кучей на интерфейсе как вариант, завести метод copy():
class Command
{
public:
    enum Type { T1, T2, };

public:

    virtual ~Command();

    virtual Command * copy() const = 0;
    virtual Type type() const = 0;

private:

    Command(const Command & сommand);                 // Реализация отсутствует
    Command & operator=(const Command & сommand);     // Реализация отсутствует
};


class Command1
{
public:
    static const Command::Type Type = Command::T1;

public:

    Command1() { ... }
    Command1(const Command1 & сommand) { ... }

    virtual Command1 * copy() const { return new Command1(*this); }
    virtual Command::Type type() const { return Type; }
};


class Command2
{
public:
    static const Command::Type Type = Command::T2;

public:

    Command2() { ... }
    Command2(const Command2 & сommand) { ... }

    virtual Command2 * copy() const { return new Command2(*this); }
    virtual Command::Type type() const { return Type; }
};


class CommandExecutor
{
public:

    void enqueueCommand(const Command & command)
    {
        inputQueue_.push(command.copy());
    }

private:
    std::queue<Command*> inputQueue_;
};
Re[2]: Хранение в контейнере объектов разного типа
От: Ytz https://github.com/mtrempoltsev
Дата: 24.11.11 05:33
Оценка: 2 (2) +2 -1
Здравствуйте, _niko_, Вы писали:

__> — деструктор абстрактным быть не может.


Еще как может. Более того так часто делают специально для абстрактных базовых классов.
Re[3]: Хранение в контейнере объектов разного типа
От: _niko_ Россия  
Дата: 24.11.11 05:59
Оценка: -1
Здравствуйте, Ytz, Вы писали:

Ytz>Здравствуйте, _niko_, Вы писали:


__>> — деструктор абстрактным быть не может.


Ytz>Еще как может. Более того так часто делают специально для абстрактных базовых классов.



class Class1
{
public:

    virtual void method1() = 0;
    virtual void method2() = 0;
};

class Class2
{
public:

    virtual void method1() = 0;
    virtual void method2() = 0;

private:

    Value m_value;
};


В первом случае (Class1) деструктор создан не будет, т.к. мы его не определили а компилятору он не нужен (соответственно об абстрактности деструктора говорить не приходится).
Во втором случае (Class2) объявляете вы деструктор или не объявляете или делаете его виртуальным и абстрактным — он будет создан в любом случае, т.к. есть внутренние объекты которые необходимо удалять.
Re[2]: Хранение в контейнере объектов разного типа
От: PM  
Дата: 24.11.11 06:08
Оценка: 2 (2) -1
Здравствуйте, _niko_, Вы писали:

__> — деструктор абстрактным быть не может.


как уже сказали, деструктор абстрактным быть может, нужно только реализовать его (не inline!):

class Command
{
    virtual ~Command() = 0;
};

Command::~Command()
{
}
Re: Хранение в контейнере объектов разного типа
От: PM  
Дата: 24.11.11 06:25
Оценка:
Здравствуйте, theCreature, Вы писали:

C>Имеется класс команда и куча производных от него конкретных команд:

[здесь был пример кода]

C>Т.к. классов команд несколько, приходится хранить в очереди не сами объекты, а указатели на них. CommandExecutor после выборки команды из очереди и ее исполнения просто уничтожает соответствующий объект. Получается, что для передачи команды в CommandExecutor::enqueueCommand необходимо создать объект конкретной команды в куче. Собственно ничего не мешает это сделать, но хочется писать код, который сложно использовать неправильно. В данном случае придется откомментировать enqueueCommand, обозначив необходимость создания объектов Command в куче и переход права владения на объект к CommandExecutor. Ну а теперь собственно сам вопрос: как лучше решается такая проблема? Исключительно комментированием условий использования или все же можно сделать реализацию поизящнее?

C>Мне на ум приходит разве что использование pimpl для Command. Но в этом случае придется создавать кучу дополнительного кода для каждого конкретного класса-команды. Так же можно воспользоваться boost::any, но очень не хочется добавлять дополнительные накладные расходы, т.к. код довольно критичен ко времени выполнения.

Про boost еще вроде не упоминали, отмечусь

Для хранения команд использовать boost::ptr_deque (или другой подходящий из Boost Pointer Container), для факта передачи владения — std::auto_ptr

class CommandExecutor {
public:
    void enqueueCommand(std::auto_ptr<Command> command)
    {
         inputQueue_.push_back(command);
    }

private:
    boost::ptr_deque<Command> inputQueue_;
};


Кстати, я думаю что опасения по поводу неверного использования кода слегка преувеличены — достаточно указать в комментариях к CommandExecutor что он владеет объектами Command (а лучше попробовать дать ему имя, говорящее об этом ). Адекватный программист не будет запихивать в CommandExecutor адреса стековых объектов, ну а неадекватный когда-нибудь получит испорченный стек.

Еще как вариант можно сделать фабрику объектов команд, но могут возникнуть сложности с передачей парамтеров в конструкторы производных классов.
Re[3]: Хранение в контейнере объектов разного типа
От: _niko_ Россия  
Дата: 24.11.11 07:10
Оценка: -4
Здравствуйте, PM, Вы писали:

PM>как уже сказали, деструктор абстрактным быть может, нужно только реализовать его (не inline!):

Ответьте на вопрос: что такое абстрактный метод класса?
— это метод класса, реализация для которого отсутствует.


Да, в С++ можно для абстрактного метода сделать реализацию, но это тонкое место этого языка программирования (если хотите бага/фича) и закладываться на это при реализации какого то класса не правильно.
Посему нужно понимать что если метод объявлен как абстрактный — реализации в этом классе у него нет, но при наследовании нам необходимо её сделать.

Делая деструктор класса абстрактным мы обязуем наследника сделать его реализацию.
Но базовый класс не должен требовать от наследника реализовывать свой деструктор!
Ведь наследник может и не иметь ни одного внутреннего объекта/переменной, зачем ему тогда реализация деструктора?
Re[2]: Хранение в контейнере объектов разного типа
От: PM  
Дата: 24.11.11 07:16
Оценка: 1 (1)
Здравствуйте, _niko_, Вы писали:

__> — деструктор абстрактным быть не может.


Ок, с аргументами, а не только с минусами. Открываем C++ Standart ISO-IEC 14882.1998(E) (у меня свежее нет пока), глава 12.4 Destructors, стих 7:

12.4 Destructors
...
7 A destructor can be declared virtual (10.3) or pure virtual (10.4); if any objects of that class or any
derived class are created in the program, the destructor shall be defined. If a class has a base class with a
virtual destructor, its destructor (whether user- or implicitly- declared) is virtual.

Re[2]: Хранение в контейнере объектов разного типа
От: 13akaEagle Россия  
Дата: 24.11.11 07:26
Оценка:
Здравствуйте, PM, Вы писали:

PM>Еще как вариант можно сделать фабрику объектов команд, но могут возникнуть сложности с передачей парамтеров в конструкторы производных классов.


Поясните, пожалуйста, какие могут возникнуть проблемы?
http://codepad.org/QuZctC4P

Смотрел boost::make_shared, пока не разобрался что там творится...
Re[3]: Хранение в контейнере объектов разного типа
От: _niko_ Россия  
Дата: 24.11.11 07:27
Оценка:
Здравствуйте, PM, Вы писали:

PM>Здравствуйте, _niko_, Вы писали:


__>> — деструктор абстрактным быть не может.


PM>Ок, с аргументами, а не только с минусами. Открываем C++ Standart ISO-IEC 14882.1998(E) (у меня свежее нет пока), глава 12.4 Destructors, стих 7:


PM>

PM>12.4 Destructors
PM>...
PM>7 A destructor can be declared virtual (10.3) or pure virtual (10.4); if any objects of that class or any
PM>derived class are created in the program, the destructor shall be defined. If a class has a base class with a
PM>virtual destructor, its destructor (whether user- or implicitly- declared) is virtual.



Нашел этот пункт в ISO/IEC 14882:2011(E): 12.4.9

Ну тут уже возразить нечего
Re[2]: Хранение в контейнере объектов разного типа
От: theCreature  
Дата: 24.11.11 07:43
Оценка:
Здравствуйте, 13akaEagle, Вы писали:
E>Так для полиморфного использования в любом случае придётся пользоваться указаелями, но наверное лучше использовать unique_ptr из нового стандарта. Что будет говорить, что объект имеет политику одиночного владения.
E>А Command разрешть создавать только фабрике, которая будет отдавать unique_ptr<Command>.

Я думал насчет фабрики, но проблема еще в том, что наследники Command имеют конструкторы с различным количеством аргументов.
Re[3]: Хранение в контейнере объектов разного типа
От: 13akaEagle Россия  
Дата: 24.11.11 09:03
Оценка:
Здравствуйте, theCreature, Вы писали:

C>Я думал насчет фабрики, но проблема еще в том, что наследники Command имеют конструкторы с различным количеством аргументов.

А так?
http://codepad.org/N4UtpPMa
Re: Хранение в контейнере объектов разного типа
От: Аноним  
Дата: 24.11.11 10:04
Оценка:
Здравствуйте, theCreature, Вы писали:
...
А если как-то так?
class Command
{
protected:
    virtual ~Command() = 0;
public:
    virtual void Destroy() = 0;
};

class HeapCommand : public Command
{
public:
    virtual void Destroy() { delete this; }
};

class StaticCommand : public Command
{
public:
    virtual void Destroy() {}
};
Re[4]: Хранение в контейнере объектов разного типа
От: johny5 Новая Зеландия
Дата: 24.11.11 10:06
Оценка: 2 (1)
E>А так?

Шо-то заребило в глазах от темплейтов
Для запрещения создания на стеке проще всего вот так:

class CCommand1
{
    static CCommand1*  Create(...) { return new CCommand1(...); }

protected:
    CCommand1(...);
}



Для указания что указатель идёт во владение, уже насоветовали использовать умные указатели в аргументе: std::auto_ptr<>, scoped_ptr<>, unique_ptr<>...
Re[3]: Хранение в контейнере объектов разного типа
От: johny5 Новая Зеландия
Дата: 24.11.11 10:11
Оценка:
Здравствуйте, 13akaEagle, Вы писали:
E>Смотрел boost::make_shared, пока не разобрался что там творится...

boost::make_shared создает объект вместе со счётчиком за один вызов new. Т.е. счётчик и объект лежат рядом.
Из очевидных минусов — пока существует хоть один weak pointer, смотрящий на уже освобождённый объект — память освобождена не будет.
Re: Хранение в контейнере объектов разного типа
От: Tpyn666  
Дата: 24.11.11 11:23
Оценка:
Чтоб не плодить темы, у меня похожая задача.
Есть такой класс

class Queue
{
public:
void AddCommand(void* lpObj, size_t sizeObj);
void SucceedSend()
{
_queue.clear();
}

private:
struct Command
{
void* lpObj;
size_t sizeObj;

~Command()
{
// Тут нужно удалить отправленный объект
// Такая конструкция само собой не подайдет delete lpObj;
// Как быть
}
};

std::list<Command> _queue;
};

Это класс в другой нитке оправляет сообщение по мере возможностей и после чистит очередь. Мне нужно удалять отправленные обькты как быть ?
Re[2]: Хранение в контейнере объектов разного типа
От: Tpyn666  
Дата: 24.11.11 11:29
Оценка:
Сори за код теги забыл.

Чтоб не плодить темы, у меня похожая задача.
Есть такой класс:


class Queue
{
    public:
    void    AddCommand(void* lpObj, size_t sizeObj);

private:
    void    SucceedSend()
    {
        _queue.clear();
    }

    struct Command
    {
        void*    lpObj;
        size_t    sizeObj;
        ~Command()
        {
            // Тут нужно удалить отправленный объект 
            // Такая конструкция само собой не подайдет delete lpObj;
            // Как быть
        }
    };    
    
    std::list<Command> _queue;
};



Это класс в другой нитке оправляет сообщение по мере возможностей и после чистит очередь. Мне нужно удалять отправленные обькты как быть ?
Re[3]: Хранение в контейнере объектов разного типа
От: johny5 Новая Зеландия
Дата: 24.11.11 13:10
Оценка:
Здравствуйте, Tpyn666, Вы писали:


T>Мне нужно удалять отправленные обькты как быть ?


Избавиться от void*
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.