我有几个类QList<ClassName *>
的属性。我使用原始指针来表示所有权。对此的访问器将返回QList<QPointer<ClassName>>
,因为我们不想分发原始指针。问题是,目前每个访问者或多或少看起来如下:
QList<QPointer<ClassName>> values;
ClassName* value;
foreach(value, m_values){
values.append(QPointer<ClassName>(value));
}
return values;
在某些情况下,我们使用的容器不同于QList
,有时容器中的值是const。现在我想知道是否有一种方法不需要我将其复制/粘贴到每个访问者,因为它感觉要么应该有更好的方法,要么我们首先做错了。
答案 0 :(得分:3)
那不是。在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
。你可以用例如std::list
和安置,或std::array
。这暴露了API的脆弱性:它对您内部使用的容器非常敏感。
通过迭代器访问容器通常是惯用的。您可以通过迭代器公开对象容器,用户应该编写只需要特定黑盒迭代器类型的代码。
E.g:
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
是一个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开始就是如此。