Qt - Каков наилучший способ сохранить указатель на что-то, хранящееся в QList? - PullRequest
0 голосов
/ 30 ноября 2018

Я довольно новичок в Qt и C ++ (работаю над C # и Java всю свою жизнь) и почти везде читал, что не должен больше использовать необработанные указатели (кроме тех, что в конструкторах и деструкторах, следуя принципу RAII).

Я в порядке, но есть проблема, которую я не могу легко решить без указателей.

Я попытаюсь объяснить мою проблему.

Я создаюQList созданного мной пользовательского класса (MyClass), который будет служить моделью для моего приложения:

QList<MyClass> modelList;

Этот QList обновляется (элементы добавляются, удаляются или обновляются) рабочим потоком на основеинформации, поступающей из сети.

Пока все хорошо.

Теперь у меня есть еще один поток (GUI), в котором есть QList рисованных элементов (MyDrawableItem).Каждый из этих элементов имеет член, который должен указывать на один из элементов в первом QList (modelList), например:

QList<MyDrawableItem> listToDraw;

где:

class MyDrawableItem
{
private:
    MyObject *pointedObject;

    //List of many other members related to drawing
}

, потому что каждый раз таймерв потоке GUI истекает, я должен обновить связанные элементы рисования на основе указанного объекта.Необходимо отметить, что безопасно хранить указатель на члены QList, как указано в документации:

Примечание: итераторы в QLinkedList и ссылки на QList-ы, выделяющие кучу, остаются действительными до тех пор, пока ссылочные элементыоставаться в контейнере.Это не верно для итераторов и ссылок на QVector и QLists без выделения кучи.

Внутренне, QList представляется как массив T, если sizeof (T) <= sizeof (void *) и T имеетбыло объявлено либо Q_MOVABLE_TYPE, либо Q_PRIMITIVE_TYPE с использованием Q_DECLARE_TYPEINFO.В противном случае QList представляется в виде массива T *, а элементы располагаются в куче. </p>

Как это возможно без использования указателей?Или, что еще лучше, каким должен быть наилучший способ выполнения такой работы?

Я нашел три возможных решения:

  1. Вместо того, чтобы сохранятьPointObject в качестве члена MyDrawableItem, сохраняйтеиндекс указанного объекта в modelList.Затем по обновлению зайдите внутрь QList.Но что, если элемент в списке будет удален?Я мог бы сигнализировать об этом, но если обновление произойдет до того, как слот будет вызван?

  2. Ничего не сохранять в качестве члена и подключить два сигнала к MyDrawableItem.Один сигнал updateDrawing(const MyObject&) обновляет элементы, относящиеся к рисованию, а другой сигнал removeDrawing() просто удаляет объект.Таким образом, мне не нужно беспокоиться о синхронизации между потоками, потому что объект MyDrawableItem никогда не будет смотреть на содержимое QList.В этом случае я мог бы обновлять элемент очень часто с большим количеством сигналов, в то время как я хочу обновлять GUI только время от времени (таймер 500 мс).

  3. Просто используйте указатели.

  4. Развивайте и используйте QSharedPointers, но я никогда не использовал их, и я не знаю, является ли это лучшим вариантом.

Я думаю, чтони одно из этих решений не является идеальным, поэтому я здесь прошу вашей помощи.

Как стандартные представления в Qt (просмотр таблицы, просмотр списка и т. д.) решают эту проблему?

КакДолжен ли я с этим справиться?

Пожалуйста, помогите мне, я думаю об этом со вчерашнего дня, и я не хочу наводить беспорядок.

Спасибо большое!

Ответы [ 2 ]

0 голосов
/ 01 декабря 2018

Дополнение к ответу @Benjamin T:

Если вы решите перейти к варианту 4 и использовать QSharedPointer (или вариант std), вы можете фактически контролировать владение с помощью «слабых указателей».Слабые указатели хранят ссылку на объект, но не владеют им.Как только последний «сильный указатель» будет уничтожен, все слабые указатели сбрасывают ссылку и сбрасываются на nullptr.

. Для вашего конкретного примера вы можете использовать QList<QSharedPointer<MyClass>> для модели и использовать QWeakPointer<MyClass> в рисованных классах.Перед рисованием вы должны создать локальный QSharedPointer<MyClass> в функции рисования и проверить его действительность.

Я все равно рекомендую вам использовать 2/3, как рекомендовано Бенджамином.Просто небольшое дополнение.

0 голосов
/ 30 ноября 2018

Это текущая тенденция в сообществе C ++, особенно если вы посмотрите на все конференции CppCon, чтобы препятствовать использованию необработанных указателей.

Эта тенденция не без продаваемой основы, как использование std::shared_ptr и std::unique_ptr, а также std::make_unique и std::make_shared могут помочь в управлении памятью и предотвратить некоторые подводные камни (например, исключение во время вызова конструктора).

Однако ничего не происходитВ этом абсолютном тренде «не использовать необработанные указатели» можно использовать необработанные указатели, а умные указатели не решают всех проблем управления памятью.

Что касается случая Qt, если вы имеете дело с производным QObjectкласс, и вы правильно родительские объекты, вы в некотором роде в безопасности, поскольку у вас есть иерархия владения , и родительские объекты QObjects уничтожат их дочерние элементы.Это очень важно, если вы также используете QThread, поскольку QObject экземпляры связаны с QThread, а все экземпляры в иерархии QObject связаны с одним и тем же потоком.

Относительно ваших решений:

  1. Плохая идея, вам нужно постоянно синхронизировать ваш список и индексы.
  2. Это может быть идея, но она меняет архитектуру вашего программного обеспечения.MyObject действительно должен знать о MyObject?Если ответ «нет», сигнал / слоты кажутся лучшим решением.Возможно, для решения проблемы с частотой обновления может потребоваться некоторая работа, но это не по теме.
  3. Может показаться хорошей идеей, особенно если ваши объекты наследуют QObject.Также вы можете захотеть использовать QVector<T *> и std::vector<T *> и позволить иерархии QObject иметь дело с продолжительностью жизни ваших объектов.Вы можете прослушать сигнал destroyed(), чтобы узнать, когда объект разрушен, или использовать QPointer.Однако это не решает проблему безопасности потоков на стороне потребителя, так как объект может быть удален в любой момент времени из другого потока.
  4. Если вы не можете сделать 2, это вариант.Вам просто нужно позаботиться о том, чтобы не было циклических зависимостей, иначе вы потеряете память.Однако, это может быть плохой идеей, если вы хотите, чтобы при удалении объекта из списка он удалялся из объекта GUI.Если вы просто сохраните общий указатель, вы никогда не узнаете, что он был удален из списка.Как заявляет @Felix, вы можете хранить слабые указатели и преобразовывать их в общие указатели только при необходимости, но если у вас есть потребители в нескольких потоках, вы никогда не сможете удалить объект.Также вы должны сделать MyObject поточно-безопасным, поскольку вы изменяете его состояния из одного потока и читаете его из другого потока.Использование опции 4 действительно меняет модель владения вашим программным обеспечением: в текущем состоянии QList владеет экземплярами, а с опцией 4 право собственности передается любому, кто получает общий указатель.

Мой личный выбор будетзаменить QList на QVector указателей.Если ваши объекты наследуются от QObject, то я бы связывал все объекты в QVector с объектом, которому принадлежит QList / QVector.Это необходимо для обеспечения правильного воспитания и связи потоков.Тогда я попробую вариант 2, и только в случае сбоя варианта 2 перейдите к варианту 4.

Вот пример:

class MyClass : public QObject
{
    Q_OBJECT
public:
    explicit MyClass(QObject *parent = nullptr);

signals:
    void stateChanged(int state);
};

class Owner : public QObject
{
    Q_OBJECT
public:
    explicit Owner(QObject *parent = nullptr);
    void addObject(MyClass *obj) {
        obj->setParent(this);
        m_objects.append(obj);
    }

    void removeObject() {
        delete m_objects.takeFirst();
    }

private:
    QVector<MyClass *> m_objects;
};



class Consumer : public QObject
{
    Q_OBJECT
public:
    explicit Consumer(QObject *parent = nullptr);

public slots:
    void onStateChanged(int state) {
        m_cachedState = state;
    }

private:
    int m_cachedState = 0;
};

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

    QThread thread;
    thread.start();

    Owner owner;
    Consumer consumer;
    consumer.moveToThread(&thread);

    MyClass *object = new MyClass();
    owner.addObject(object);
    QObject::connect(object, &MyClass::stateChanged, &consumer, &Consumer::onStateChanged);

    return app.exec();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...