Я использую необработанные указатели, чтобы сигнализировать владение.
Не делайте этого. В 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 по крайней мере.
Ну, две мысли: 1) Я бы переосмыслил API такого класса - действительно ли нужно возвращать QList? Не лучше ли попросить владельца действовать на элементы или определенный элемент? Помните один из принципов ООП: «расскажи, не спрашивай». 2) Почему класс владельца не хранит список QPointers? Почему грубые указатели выражают право собственности, это кажется мне странным. В чем причина этого? –
Другая мысль, зачем вообще предоставлять аксессуар? – user3528438
@ V.K. Собственный класс может хранить список 'QPointer', но они являются слабыми указателями, поэтому им еще нужно хранить объекты где-то. Таким образом, он должен будет иметь для членов: как коллекцию, * и * коллекцию 'QPointer's. –