C++: в списке базовых объектов производная память дает неожиданные результаты при передаче по ссылке

У меня возникают некоторые проблемы с пониманием границ использования ссылок вместо указателей: как я могу передать производный объект без виртуальных функций, только набор данных, в список базовых объектов?

В моем приложении есть объект события и очередь:

class Event {
    String name;
public:
    Event(const String &name_);
};

class KeyEvent : public Event {
public:    
    int key;
};

std::queue<Event> eventsQueue;

Когда я преобразую объекты из очереди в их исходные типы, я получаю неопределенные значения во всех переменных, чего никогда не было с указателями. В общем,

while(!eventsQueue.empty()) {
    auto &e = eventsQueue.front();
    if(e.name == "keyPress") {
        std::cout << static_cast<KeyEvent &>(e).key;
    }  
}

дает мне бред: http://ideone.com/rbzGJV

Есть ли способ исправить это без использования указателей? Есть ли способ иметь ссылку внутри контейнера, как std::queue?

2 ответа

Решение

Событие - это блок памяти с определенным размером, который является размером объекта String. KeyEvent - это блок памяти с размером sizeof(Event) + sizeof(int), который на 4 байта (вероятно) больше. Вы храните вещи в контейнере типов Event, поэтому каждый элемент в контейнере имеет размер Event, и int... ну... он упадет с конца в неопределенную область поведения. За счет хранения указателей вместо этого каждый элемент в контейнере будет иметь размер типа указателя, независимо от того, на что он указывает.

Я понятия не имею, что вы сделали, чтобы объединить эти объекты KeyEvent в контейнер Event, но что бы это ни было, я рекомендую больше этого не делать!

PS Ваша фраза предполагает Java-фон? В C++ "Событие е;" это не ссылка, это событие. Событие & e; это ссылка на событие. Событие * е; указатель на событие; Event & e реализована внутри как указатель в большинстве компиляторов C++, но вам не нужно об этом беспокоиться. Я е; думаю, что в поведении ближе всего к тому, что сделал бы Java, если бы вы сказали Event e; является std::shared_ptr<Event>

Ваша очередь содержит объекты Event, поэтому, когда вы нажимаете KeyEvent, это неявное приведение к Event, а затем объект Event создается в очереди путем копирования. Затем вы приводите объект Key к событию KeyEvent, что означает, что вы начинаете читать память для ключевого поля извне объекта, отсюда и мусор.

Если вы хотите иметь полиморфные типы, вы должны использовать указатели.

Другие вопросы по тегам