LINUX.ORG.RU

Наследованный класс в QVector

 ,


0

3

Помогите разобраться.

У меня есть два класса: Родительский и Наследуемый. Есть QVector для Родительского класса. Но в него я передаю экземпляр наследованного класса. После чего достаю экземпляр обратно и получаю вмесо этого экземпляр Родительского класса. Я понимаю, почему так происходит. Но не знаю, как сделать так, чтобы возвращался экземпляр исходного класса. При этом имея возможность засунуть в QVector различные варианты наследников.

P.S. Я очень плохо знаком с С++, поэтому не надо пугать меня Царем и другими авторитетами. Скорее всего я не знаю, кто это.

Поясняющий пример:

#include <QDebug>
#include <QVector>

class Parent
{
private:
    QString m_str;
protected:
    QString str() const {return m_str;}
public:
    Parent(QString str = "EmptyParent") : m_str(str){ }
    Parent(const Parent& oth) : m_str(oth.m_str) {}
    virtual void sayStr() const {qDebug() << m_str;}
    virtual Parent& operator=(const Parent& oth) {return *new Parent(oth);}
};

class Child : public Parent
{
private:
    QString m_chstr;
public:
    Child(QString str = "EmptyChild") : Parent("Ololo"), m_chstr(str) {}
    Child(const Child& oth) : Parent(oth), m_chstr(oth.m_chstr) {}
    virtual Child& operator=(const Child& oth) {return *new Child(oth);}
    virtual void sayStr() const Q_DECL_OVERRIDE {qDebug() << m_chstr;}
};



int main(int argc, char *argv[])
{
    QVector<Parent> vec;
    Child el("Child");
    vec.push_back(el);
    vec.at(0).sayStr();
    return 0;
}



Последнее исправление: r_a_vic (всего исправлений: 1)

Наверное всё дело в том, что push_back копирует аргумент? Код не запускал.

Тогда решением будет создать вектор не из Parent, а из Parent*

Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

Да. и .at(0) - тоже. Использовать указатели, касты и т.п. - не очень удобно, так как точно же не известно, какой из наследованных классов в QVector положили. Но похоже, что по другому не решить. Какая-то неожиданная проблема, на которую вообще никак не рассчитывал.

r_a_vic
() автор топика
Ответ на: комментарий от Crocodoom

А, хотя нет. Если положить в вектор указатели, то должен быть вызван перегруженный метод. Что-то я затупил. Спасибо.

r_a_vic
() автор топика
Ответ на: комментарий от r_a_vic

Использовать указатели, касты и т.п. - не очень удобно, так как точно же не известно, какой из наследованных классов в QVector положили.

С кастами понятно. Но в чём проблема использовать указатели?

QVector<Parent> vec;
vec.push_back(new Child("Child"));
vec.at(0)->sayStr();
Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

Ну я думал, что C++ - это безопасный язык.

Придется отслеживать удаления, и удалять после использования.

А в остальном проблем нет, ты прав.

r_a_vic
() автор топика
Ответ на: комментарий от Crocodoom

Про Qt-овские я уже читал и даже использую, правда, в другом месте. Придется и тут, видимо, их добавить.

Спасибо!

r_a_vic
() автор топика
Последнее исправление: r_a_vic (всего исправлений: 1)

Оффтоп: меня одного операторы присваивания смущают?

fluorite ★★★★★
()

Используй указатели. Иначе будут разные размеры Родителя и Наследника и все распидорасит.

iamweasel
()

Как уже написали, эти грабли называются срезка типа. Используй умные указатели в контейнерах.

Так писать оператор присваивания неправильно

virtual Child& operator=(const Child& oth) {return *new Child(oth);}

И напиши виртуальные деструкторы, иначе получишь утечку памяти.

four_str_sam
()
Ответ на: комментарий от four_str_sam

ОК. Про деструкторы учту. Всегда было интересно, почему надо писать виртуальный деструктор. Теперь понятно.

А вот с операторами что не так? Я их из какого-то примера взял.

r_a_vic
() автор топика
Последнее исправление: r_a_vic (всего исправлений: 2)

Насколько мне известно, в С++ вообще нельзя делать вот так

    Child a = ...
    Parent b = (Parent)a;
Потому что размер родителя и размер потомка могут не совпадать. Приводить так можно только указатели. Посему оберни Содержимое вектора в указатели или умные указатели.

Aswed ★★★★★
()
Ответ на: комментарий от r_a_vic

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

Правильно оператор = писать как тут

Код как у тебя даст утечку памяти:

Child chld("foo");
chld = Child("bar");

Для обнаружения утечек советую начать пользоваться valgrind

four_str_sam
()
Ответ на: комментарий от four_str_sam

Хорошо. Поизучаю. valgrind много раз хотел начать использовать, но пока так и не разобрался, как.

r_a_vic
() автор топика
Ответ на: комментарий от Aswed

Насколько мне известно, в С++ вообще нельзя делать вот так

Можно. Получишь Parent сконструированный из Child.

slovazap ★★★★★
()
Ответ на: комментарий от r_a_vic

Без каких-то специальных требований разбираться особо не в чем:

valgrind --fullpath-after=$PWD/ --track-origins=yes --track-fds=yes --log-file=valgrind.report --leak-check=full ./program
vim valgrind.report
В Qt Creator даже интеграция какая-то есть.

xaizek ★★★★★
()
Ответ на: комментарий от xaizek

Хинт:

Если грубо

Подразумевалось, что мысль дойдёт, а реализацией уже займётся ТС так, как ему надо

XMs ★★★★★
()
Ответ на: комментарий от four_str_sam

Всем спасибо еще раз. Многие вещи разъяснили. А насоветуйте книжек, где такие вещи объясняются, пожалуйста.

r_a_vic
() автор топика
Ответ на: комментарий от slovazap

И что, это всегда полностью безопасно? Прям можно без презерватива указателя? А если child больше чем parent по размеру, и данные, которые вылезают за размер parent, используются в виртуальных методах, которые могут быть вызваны у parent?

Aswed ★★★★★
()
Ответ на: комментарий от r_a_vic

Можно полистать c++faq. Там как-раз есть про оператор присвоения и виртуальные деструкторы

Для системного изучения рекомендую:

  • Bjarne Stroustrup - The C++ Programming Language (4th Edition) - 2013
  • Scott Meyers - Effective Modern C++
  • Anthony Williams - C++ Concurrency in Action: Practical Multithreading
four_str_sam
()
Ответ на: комментарий от Aswed

И что, это всегда полностью безопасно? Прям можно без презерватива указателя? А если child больше чем parent по размеру, и данные, которые вылезают за размер parent, используются в виртуальных методах, которые могут быть вызваны у parent?

Ты меня не понял. Ты получишь голый Parent и только, никаких тут Child вообще не будет в помине, не будет данных Child, нельзя будет вызывать методы Child.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

Нет я понял. Я говорю вот про такую ситуацию.

class Parent {
    public:
    virtual void call() {};
}

class Child : public Parent {
    public:
    int value;
    virtual void call() {
        value = 7;
    }
}

...

vector<Parent> v;
v.push_back(Child());
v[0].call();

Что в такой ситуации. call уже не просто метод Child, а запись в виртуальной таблице, которая должна сохранится в нем при приведении к parent, а value, я так понимаю обрежется. Так что должно произойти при таком вызове? Ошибка сегментирования или будет вызван метод call от Parent?

Aswed ★★★★★
()
Ответ на: комментарий от slovazap

Вот сейчас попробовал это провернуть у себя на машине(mingw компилятор), он отказался мне каким либо образом приводить child к parent в таком виде.

.\virtual_hack.cpp: In function 'int main(int, const char**)':
.\virtual_hack.cpp:27:43: error: invalid cast from type 'Child' to type 'Parent'
     v.push_back(reinterpret_cast<Parent>(c));
Как вообще ты и топикстартер такое компилировали?

Aswed ★★★★★
()
Ответ на: комментарий от Aswed

которая должна сохранится в нем при приведении к parent

Не должна и нет никакого «приведения». Есть конструирование Parent, при котором в его указатель на vtable кладётся, сюрприз-сюрприз, указатель на его vtable.

а value, я так понимаю обрежется

Нет, value просто умрёт вместе со временным Child().

Ошибка сегментирования или будет вызван метод call от Parent?

Разумеется Parent::call().

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

Ну прост у меня .push_back(Child()) тоже не работало и я начал приводить к типу руками, в надежде что хоть так скомпилится, но нет.

Aswed ★★★★★
()
Ответ на: комментарий от Aswed

«Не работать» может только голова, и тогда вместо чтения ошибки начинают программировать тыком.

slovazap ★★★★★
()
Ответ на: комментарий от plgrm44

А причем тут утечка памяти? Использование указателей напрямую - уже не безопасно. Потому что указатель может быть уже и не валидным.

r_a_vic
() автор топика
Ответ на: комментарий от XMs

Если грубо

Это не грубо, а очень вежливо. Если реальный тип известен из архитектуры кода, то достаточно static_cast

annulen ★★★★★
()
Последнее исправление: annulen (всего исправлений: 1)
Ответ на: комментарий от r_a_vic

Наконец конструктивный ответ. Спасибо.

Aswed ★★★★★
()
Ответ на: комментарий от four_str_sam

c++faq отличная штука. Почему-то гуглом ни разу на нее не натыкался.

r_a_vic
() автор топика
Ответ на: комментарий от r_a_vic

Нет, правильный ответ «могу и должен, зачем мне сдался QVector?».

BruteForce ★★★
()
Ответ на: комментарий от annulen

Ахахахах, ну да, в данном случае хрен редьки слаще.

BruteForce ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.