2016-04-01 2 views
0

У меня есть несколько классов с атрибутами типа QList<ClassName *>. Я использую необработанные указатели, чтобы сигнализировать владение. Аксессор для этого вернет QList<QPointer<ClassName>>, так как мы не хотим раздавать исходные указатели. Проблема заключается в том, что в данный момент каждом аксессоре более или менее выглядит следующим образом:Есть ли более простой способ конвертировать из QList <Class*> в QList <QPointer <Class>>?

QList<QPointer<ClassName>> values; 
ClassName* value; 
foreach(value, m_values){ 
    values.append(QPointer<ClassName>(value)); 
} 
return values; 

В некоторых случаях мы используем различные контейнеры, чем QList, а иногда и значение в контейнерах Уст. Теперь я задаюсь вопросом, есть ли способ, который не требует, чтобы я копировал/вставлял это к каждому аксессуару, потому что он чувствует, что либо должен быть лучший способ, либо мы делаем что-то неправильно в первую очередь.

+2

Ну, две мысли: 1) Я бы переосмыслил API такого класса - действительно ли нужно возвращать QList? Не лучше ли попросить владельца действовать на элементы или определенный элемент? Помните один из принципов ООП: «расскажи, не спрашивай». 2) Почему класс владельца не хранит список QPointers? Почему грубые указатели выражают право собственности, это кажется мне странным. В чем причина этого? –

+0

Другая мысль, зачем вообще предоставлять аксессуар? – user3528438

+0

@ V.K. Собственный класс может хранить список 'QPointer', но они являются слабыми указателями, поэтому им еще нужно хранить объекты где-то. Таким образом, он должен будет иметь для членов: как коллекцию, * и * коллекцию 'QPointer's. –

ответ

2

Я использую необработанные указатели, чтобы сигнализировать владение.

Не делайте этого. В C++ 11 используйте std::unique_ptr или std::shared_ptr, в зависимости от ситуации. Сам Qt использует указатель QObject, не подразумевая права собственности. Например. QObject::children() возвращает QObjectList = QList<QObject*>. Я думаю, что вы сильно преувеличиваете вещи, используя QPointer. Чтобы минимально повлиять на другой код, просто верните QObjectList.

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

class MyClass { 
    std::list<QObject> m_gadgets; 
    QList<QPointer<QObject>> m_userGadgets; // or std::list<...> 
public: 
    typedef const std::list<QObject> Gadgets; 
    MyClass() { 
    m_gadgets.emplace_back(...); 
    ... 
    m_userGadgets.reserve(m_gadgets.size()); 
    for (auto & gadget : m_gadgets) 
     m_userGadgets.push_back(QPointer(&gadget)); 
    } 
    Gadgets & gadgets() const { return m_userGadgets; } 
}; 

Как вы видите, вам не нужно использовать сырые указатели для хранения QObject s. Вы можете использовать, например. std::list и установка, или std::array. Это обеспечивает хрупкость вашего API: он очень чувствителен к контейнеру, который вы используете внутри страны.

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

т.д .:

class MyClass { 
    std::list<QObject> m_gadgets; 
public: 
    typedef std::list<QObject>::const_iterator GadgetsConstIterator; 
    GadgetsConstIterator gadgetsBegin() const { return m_gadgets.begin(); } 
    GadgetsConstIterator gadgetsEnd() const { return m_gadgets.end(); } 
}; 

void gadgetsUser(MyClass::GadgetsConstIterator begin, MyClass::GadgetsConstIterator end); 

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

Вы также можете открыть контейнеры объектов в качестве общего контейнера, где пользователю должно быть дано указание использовать std::begin(container) и std::end(container) для доступа к контейнеру. Таким образом, вы можете даже использовать сырые массивы C (Дрожь):

class MyClass { 
    QObject m_gadgets[5]; 
    // or 
    std::array<QObject, 5> m_gadgets; 
    // or 
    std::list<QObject> m_gadgets; 
public: 
    // adjust as necessary 
    typedef QObject Gadgets[5]; 
    typedef const QObject ConstGadgets[5]; 
    ConstGadgets & gadgets() const { return reinterpret_cast<ConstGadgets&>(m_gadgets); } 
    Gadgets & gadgets() { return m_gadgets; } 
} 

void gadgetsUser1(MyClass::ConstGadgets & gadgets) { 
    for (auto gadget : gadgets) 
    qDebug() << gadget.metaObject()->className(); 
} 

void gadgetsUser2(MyClass::ConstGadgets & gadgets) { 
    for (auto it = std::begin(gadgets); it != std::end(gadgets); it++) 
    qDebug() << gadget.metaObject()->className(); 
} 

Наконец, вы также можете полностью скрыть тип коллекции, и только разоблачить печеный forEach, который проходит через контейнер:

class MyClass { 
    std::list<QObject> m_gadgets; 
public: 
    template <typename F> forEachGadget(F && fun) const { 
    for (auto const & gadget : m_gadget) fun(gadget); 
    } 
    template <typename F> forEachGadget(F && fun) { 
    for (auto & gadget : m_gadget) fun(gadget); 
    } 
}; 

void OtherClass::gadgetUser(MyClass & c) { 
    c.forEachGadget([this](const QObject & gadget) { qDebug() << &gadget; } 
} 

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

Наконец, вы не должны забывать, что QObject is-a QObject контейнер. Вы можете предложить большую гибкость, просто сохраняя объекты как дочерние объекты контейнера.Таким образом, вы можете вернуть список объектов и дерева, используя один и тот же тип контейнера:

class MyClass { 
    QObject m_gadgets; 
public: 
    MyClass() { 
    new QObject(&m_gadgets); // list element 1 
    new QObject(&m_gadgets); // list element 2 
    } 
    const QObject & gadgets() const { return m_gadgets; } 
} 

void gadgetsUser(const QObject & gadgets) { 
    for (auto gadget : gadgets.children()) { qDebug() << gadget; } 
    // or, DFS of a tree 
    for (auto gadget : gadgets.children()) { 
    qDebug() << gadget; 
    gadgetsUser(*gadget); 
    } 
    // or, BFS of a tree 
    for (auto gadget : gadgets.children()) 
    qDebug() << gadget; 
    for (auto gadget : gadgets.children()) 
    gadgetsUser(*gadget); 
} 

Обратите внимание, что QObject держит свои ребенок в качестве внутреннего списка с стабильным порядком. QObject::children() просто возвращает ссылку на внутренний список детей, поэтому он имеет очень небольшую стоимость O(1). Добавление дочернего объекта к объекту добавляет объект во внутренний список дочерних элементов, хотя это и не задокументировано, так было с Qt 4.0 по крайней мере.

+0

Мне нужно было немного дольше, чтобы переварить ответ. Я довольно новичок в C++, и, начиная с Java, он становится очень запутанным. Я, вероятно, немного переработаю дизайн, поэтому мне не нужно делать преобразования, которые мне нужны в моем вопросе. Хотя я не уверен, что что-нибудь из этого ответа повлияет на мое решение, оно определенно помогло понять проблемы. – ralgrad

+0

@ralgrad Вы обязательно должны получить копию 4-го издания * языка программирования C++ * от Strostrup; он охватывает C++ 11. Не получилось раньше. И прочитайте хотя бы половину. C++ и Java имеют принципиально разные идиомы в отношении времени жизни объектов и управления ресурсами, способ мышления Java приведет вас к очень неудобному и ненужно неэффективному коду. Это необходимо для чтения. Вы можете быть удивлены, как простой современный C++ может быть - часто проще, чем Java! Не утруждайте себя этими идиомами pre-C++ 11, которые устарели или перечеркнуты C++ 11. –

Смежные вопросы