[英]Qt: How to use Qt's Smartpointers
I have experience in "old-fashioned" C++ programming (ie I care myself about pointers and memory management). 我有“老式”C ++编程经验(即我关心指针和内存管理)。 I do want to make use of modern concepts though.
我确实想利用现代概念。
Since my application makes heavy use of Qt, I'd like to use Qt's smartpointers. 由于我的应用程序大量使用Qt,我想使用Qt的智能指针。 I am however somewhat confused about smartpointers in general, and their use in Qt.
然而,我对一般的智能指针以及它们在Qt中的使用感到有些困惑。
1.) As far as I understand, if I derive from QObject
, I should better stick to Qt's object tree and ownership model and forget about smartpointers. 1.)据我所知,如果我从
QObject
派生,我应该更好地坚持Qt的对象树和所有权模型,而忘记智能指针。 Correct? 正确?
2.) In C++ I can get by with std::shared_ptr
and std::unique_ptr
. 2.)在C ++中,我可以使用
std::shared_ptr
和std::unique_ptr
。 What are the equivalent smart pointers in Qt? Qt中的等效智能指针是什么?
Suppose I have the following code: 假设我有以下代码:
QList<MyObject *> * foobar(MyOtherObject *ptr) {
// do some stuff with MyOtherObject
QList<MyObject* > ls = new QList<MyObject*>();
for(int i=0;i<10;i++) {
MyObject* mi = new MyObject();
...
ls.insert(mi);
}
return ls;
}
int main() {
MyOtherObject* foo = new MyOtherObject();
QList<MyObject*> *bar = foobar(foo);
// do stuff
// and don't care about cleaning up?!
}
3.) How to translate the above snippet into a version using smartpointers? 3.)如何使用智能指针将上述代码段翻译成版本?
4.) In particular: Should I change function signature into using smartpointers? 4.)特别是:我应该将功能签名更改为使用智能指针吗? It seems to create quite complex type signatures (return type and passed arguments).
它似乎创建了非常复杂的类型签名(返回类型和传递的参数)。 Also what if some "legacy" function calls another function - is it better to write function signatures with raw pointers, and use smartpointers only "inside" functions?
另外,如果某些“遗留”函数调用另一个函数 - 使用原始指针编写函数签名更好,并且仅使用智能指针“内部”函数吗?
5.) What smartpointer should replace ls
in the function foobar
? 5.)什么smartpointer应该替换功能
foobar
ls
? What is the pointer type that should be used for mi
, ie the objects stored in the QList
? 什么是应该用于
mi
的指针类型,即存储在QList
的对象?
You are pretty much forced to use Qt
idiom of owning raw pointers for GUI objects, as QWidget
derived types will assume ownership of child elements. 你几乎被迫使用拥有GUI对象的原始指针的
Qt
习惯用法,因为QWidget
派生类型将承担子元素的所有权。
Elsewhere, you should as much as possible avoid using any type of pointer. 在其他地方,你应该尽可能避免使用任何类型的指针。 Most of the time you can pass a reference.
大多数时候你可以传递参考。 If you need polymorphic ownership, then use
std::unique_ptr
. 如果您需要多态所有权,请使用
std::unique_ptr
。 In very rare cicrcumstances, you have multiple independent lifetimes that need ownership of a shared resource, for which you use std::shared_ptr
. 在非常罕见的情况下,您有多个独立生命周期需要共享资源的所有权,您使用
std::shared_ptr
。
The Qt collection classes also interact badly with modern C++ constructs, eg Qt集合类也与现代C ++构造非常相互影响,例如
extern QList<Foo> getFoos();
for (const Foo & foo : getFoos())
{ /*foo is a dangling reference here*/ }
for (const Foo & foo : std::as_const(getFoos()))
{ /*This is safe*/ }
Your snippet would be 你的片段会是
std::vector<std::unique_ptr<MyObject>> foobar(MyOtherObject & obj) {
// do some stuff with MyOtherObject
std::vector<std::unique_ptr<MyObject>> ls;
for(int i=0;i<10;i++)
{
ls.emplace_back(std::make_unique<MyObject>());
...
}
return ls;
}
int main() {
MyOtherObject foo = MyOtherObject;
auto bar = foobar(foo);
// do stuff
// destructors do the cleanup automatically
}
First of all, modern C++ allows you to use values, and Qt supports that. 首先,现代C ++允许您使用值,Qt支持它。 Thus, the default should be to use
QObject
s as if they were non-movable, non-copyable values. 因此,默认值应该是使用
QObject
,就像它们是不可移动的,不可复制的值一样。 Your snippet, in fact, requires no explicit memory management at all. 实际上,您的代码片段根本不需要明确的内存管理。 And this does not clash at all with the fact that the objects have a parent.
并且这与对象具有父对象的事实完全没有冲突。
#include <QObject>
#include <list>
using MyObject = QObject;
using MyOtherObject = QObject;
std::list<MyObject> makeObjects(MyOtherObject *other, QObject *parent = {}) {
std::list<MyObject> list;
for (int i = 0; i < 10; ++i) {
#if __cplusplus >= 201703L // C++17 allows more concise code
auto &obj = list.emplace_back(parent);
#else
auto &obj = (list.emplace_back(parent), list.back());
#endif
//...
}
return list;
}
int main() {
MyOtherObject other;
auto objects = makeObjects(&other, &other);
//...
objects.erase(objects.begin()); // the container manages lifetimes
//
}
C++ has strict object destruction order, and objects
is guaranteed to be destroyed before other
. C ++具有严格的对象销毁顺序,并且保证
objects
在other
之前被销毁。 Thus, by the time other.~QObject()
runs, there are no MyObject
children, and thus there's no issue with double-deletion. 因此,当
other.~QObject()
运行时,没有MyObject
子MyObject
,因此双删除没有问题。
In general, here are the viable approaches to storing QObject
collections, with their requirements: 通常,以下是存储
QObject
集合的可行方法,以及它们的要求:
std::array
- all elements of same type, fixed size, cannot be returned std::array
- 所有相同类型的元素,固定大小,不能返回
std::list
- all elements of same type, doesn't have a RandomAccessIterator
, container owns the objects std::list
- 所有相同类型的元素,没有RandomAccessIterator
,容器拥有对象
std::deque
- all elements of same type, has RandomAccessIterator
but doesn't allow erase
since your objects are not MoveAssignable
(but of course can be cleared/destroyed), container owns the objects std::deque
- 相同类型的所有元素,具有RandomAccessIterator
但不允许erase
因为您的对象不是MoveAssignable
(当然可以清除/销毁),容器拥有对象
std::vector<std::unique_ptr<BaseClass>>
- elements of any type ie it is a polymorphic container, container owns the objects std::vector<std::unique_ptr<BaseClass>>
- 任何类型的元素,即它是一个多态容器,容器拥有对象
std::vector<QObject*>
or QObjectList
- non-owning, non-tracking container. std::vector<QObject*>
或QObjectList
- 非拥有,非跟踪容器。 Qt code is full of QObjectList = QList<QObject*>
. Qt代码充满了
QObjectList = QList<QObject*>
。
QObject
(sic!) - elements of any QObject
type, container optionally owns the objects, container tracks the object lifetime, the element pointer can be used to remove the object from the container; QObject
(sic!) - 任何QObject
类型的元素,容器可选地拥有对象,容器跟踪对象的生命周期,元素指针可用于从容器中删除对象; only one container can hold a given object, uses a bare vector to store the objects and thus additions/removals of children are O(N)
. 只有一个容器可以容纳给定的对象,使用裸向量来存储对象,因此子项的添加/删除是
O(N)
。
When storing the objects themselves, and not their collections, with object lifetime being the same as the containing scope, it's easiest to keep them as values. 当存储对象本身而不是它们的集合时,对象生命周期与包含范围相同,最简单的方法是将它们保存为值。 For example:
例如:
class ButtonGrid : public QWidget {
static constexpr int const N = 3;
QGridLayout m_gridLayout{this};
QLabel m_label;
std::array<QPushButton, N*N> m_buttons;
public:
ButtonGrid(QWidget *parent = {}) : QWidget{parent} {
int r = 0, c = 0;
m_gridLayout.addWidget(&m_label, r, c, 1, N);
r ++;
for (auto &b : m_buttons) {
m_gridLayout.addWidget(&b, r, c);
c ++;
if (c == N)
c = 0, r ++;
}
}
};
Now, to answer your questions: 现在,回答你的问题:
Should I better stick to Qt's object tree and ownership model? 我应该更好地坚持Qt的对象树和所有权模型吗? There's no choice in that matter: that model is there and it can't be disabled.
在那个问题上没有选择:那个模型在那里,不能被禁用。 But it's designed as a catch-all and you can preempt it.
但它被设计成一个包罗万象,你可以抢占它。
QObject
ownership model only ensures that children don't outlive the parent. QObject
所有权模型仅确保子项不会超过父项。 It prevents resource leaks. 它可以防止资源泄漏。 You're free to end the childrens' lives before the parent dies.
在父母去世之前,你可以自由地结束孩子的生命。 You're also free to have parentless objects.
你也可以自由地拥有无父对象。
What are the equivalent smart pointers in Qt? Qt中的等效智能指针是什么? It doesn't matter.
没关系。 You're writing C++ - use
std::shared_ptr
and std::unique_ptr
. 你正在编写C ++ - 使用
std::shared_ptr
和std::unique_ptr
。 There's no benefit to using Qt's equivalents since Qt 5.7: from that version onwards, Qt requires C++11 support in the compiler, and thus those pointers must be supported. 从Qt 5.7开始,使用Qt的等价物没有任何好处:从那个版本开始,Qt需要在编译器中支持C ++ 11,因此必须支持这些指针。
How to translate the above snippet into a version using smart pointers? 如何使用智能指针将上述代码段转换为版本? There's no need to.
没有必要。 Keep objects by value.
按值保留对象。 Perhaps you need to have a different snippet that would actually require the use of smart pointers.
也许您需要一个实际上需要使用智能指针的不同代码段。
Should I change function signature into using smart pointers? 我应该将功能签名更改为使用智能指针吗? No. You haven't motivated any smart pointer use.
不,你没有动机使用任何智能指针。
N/A N / A
1.) As far as I understand, if I derive from QObject, I should better stick to Qt's object tree and ownership model and forget about smartpointers.
1.)据我所知,如果我从QObject派生,我应该更好地坚持Qt的对象树和所有权模型,而忘记智能指针。 Correct?
正确?
Yes. 是。 When using
QObjects
, I would recommend to rely on it's parent - child model to manage memory. 使用
QObjects
,我建议依靠它的父子模型来管理内存。 It works well and you can't avoid it altogether, so use that. 它运作良好,你无法完全避免它,所以使用它。
2.) In C++ I can get by with std::shared_ptr and std::unique_ptr.
2.)在C ++中,我可以使用std :: shared_ptr和std :: unique_ptr。 What are the equivalent smart pointers in Qt?
Qt中的等效智能指针是什么?
There is QSharedPointer
and QScopedPointer
, which is somewhat similar to unique_ptr
, but doesn't support move semantics. 有
QSharedPointer
和QScopedPointer
,它有点类似于unique_ptr
,但不支持移动语义。 IMHO there is no reason to use these nowadays, just use the smart pointers provided by standard library (to manage lifetime of objects that do not derive from QObject
). 恕我直言,现在没有理由使用这些,只需使用标准库提供的智能指针(管理不是从
QObject
派生的对象的生命周期)。
4.) In particular: Should I change function signature into using smartpointers?
4.)特别是:我应该将功能签名更改为使用智能指针吗? It seems to create quite complex type signatures (return type and passed arguments).
它似乎创建了非常复杂的类型签名(返回类型和传递的参数)。 Also what if some "legacy" function calls another function - is it better to write function signatures with raw pointers, and use smartpointers only "inside" functions?
另外,如果某些“遗留”函数调用另一个函数 - 使用原始指针编写函数签名更好,并且仅使用智能指针“内部”函数吗?
Generally - use smart pointers only to manage memory = use them only when there is an ownership relation. 通常 - 仅使用智能指针来管理内存=仅在存在所有权关系时使用它们。 If you just pass an instance to a function that works with it but doesn't take ownership, pass just a plain old pointer.
如果您只是将一个实例传递给一个与它一起工作但不取得所有权的函数,那么只传递一个普通的旧指针。
Concerning your example, it's a bit hard to say without context. 关于你的例子,没有上下文就很难说。 Both
ls
and mi
could be a unique_ptr
. ls
和mi
都可以是unique_ptr
。 Better, you could just allocate ls
on stack. 更好,你可以在堆栈上分配
ls
。 And even better, avoid using QList
and use std::vector
or something like that. 更好的是,避免使用
QList
并使用std::vector
或类似的东西。
- As far as I understand, if I derive from QObject, I should better stick to Qt's object tree and ownership model and forget about smartpointers.
据我了解,如果我从QObject派生,我应该更好地坚持Qt的对象树和所有权模型,而忘记智能指针。 Correct?
正确?
Better to say "it depends". 最好说“这取决于”。 First, you should know what thread affinity in Qt is.
首先,您应该知道Qt中的线程亲和性 。 Good example -
QThread worker
. 很好的例子 -
QThread worker
。
QObject
uses parent-child memory management, so if the parent is destroyed, then all its children will be destroyed too. QObject
使用父子内存管理,因此如果父代被销毁,那么它的所有子代也将被销毁。 In the "Qt way", it is enough to manage only root object lifetime. 在“Qt方式”中,仅管理根对象生存期就足够了。 It is very easy, when you create
QObject
-based classes on heap (for example, with new
operator). 在堆上创建基于
QObject
的类(例如,使用new
运算符)时非常容易。 But there are some exceptions: 但也有一些例外:
Classes with parent-child relations should belong to the same thread. 具有父子关系的类应属于同一个线程。 You may use
QObject::moveToThread()
method to control thread affinity. 您可以使用
QObject::moveToThread()
方法来控制线程关联。 So if instances of related objects should belong to different threads, they may not have parent-child relations. 因此,如果相关对象的实例应属于不同的线程,则它们可能不具有父子关系。
There is a pattern to auto-delete objects that belong to different threads. 有一种模式可以自动删除属于不同线程的对象。 For example, if we should delete
p2
when p1
is destroyed: 例如,如果我们应该在销毁
p1
时删除p2
:
QThread t; t.start(); QObject *p1 = new MyClass1{}; p1->moveToThread( &t ); p1->doSomeWork(); QObject *p2 = new MyClass2{}; QObject::connect( p1, &QObject::destroyed, p2, &QObject::deleteLater ); // here
Object p2
will be destroyed when you delete p1
. 删除
p1
时将破坏对象p2
。
We've employed a useful method above: QObject::deleteLater()
. 我们采用了一种有用的方法:
QObject::deleteLater()
。 It schedules object deletion asap. 它尽快安排对象删除。 It may be used in several cases:
它可能在几种情况下使用:
You need to delete an object from within a slot or during emitting of a signal. 您需要从插槽内或发出信号期间删除对象。 You should not delete such objects directly, because it may cause problems.
您不应该直接删除此类对象,因为它可能会导致问题。 Example: self-destroying button (somewhere in
MyClass
): 示例:self-destroying按钮(
MyClass
某个位置):
auto btn = new QPushButton{/*...*/}; QObject::connect( btn, &QPushButton::clicked, this, &MyClass::onClicked ); void MyClass::onClicked() { auto p = qobject_cast<QPushButton *>( sender() ); // delete p; // Will cause error, because it is forbidden to delete objects during signal/slot calls p->deleteLater(); // OK, deletion will occurs after exit from the slot and will be correct }
For working with QObject-based pointer there is a QPointer
helper. 对于使用基于QObject的指针,有一个
QPointer
助手。 It will not automatically delete an object, but it will be valid all the time. 它不会自动删除对象,但它始终有效。 It will contain a reference to an object or a
nullptr
. 它将包含对象或
nullptr
的引用。 When object is removed, all its QPointer
instances will be nulled automatically. 删除对象后,其所有
QPointer
实例将自动为空。 Example: 例:
class Foo : public QObject { /* ... */ };
class Bar
{
public:
void setup( Foo * foo )
{
p = foo;
}
void use()
{
if ( p )
{
p->doSomething(); // Safe
}
}
private:
QPointer<Foo> p;
};
// ...
Foo * foo = new Foo{};
Bar bar;
bar.setup( foo );
delete foo; // or not delete
bar.use(); // OK, it's safe
Note, that QPointer
uses the QObject::destroyed()
signal for managing internal reference, so using it to keep a large amount of objects may cause performance issues (only on mass creation/destruction). 注意,
QPointer
使用QObject::destroyed()
信号来管理内部引用,因此使用它来保留大量对象可能会导致性能问题(仅限于大量创建/销毁)。 Performance with accessing to this pointers is same as with raw pointers. 访问此指针的性能与原始指针相同。
- In C++ I can get by with
std::shared_ptr
andstd::unique_ptr
.在C ++中,我可以使用
std::shared_ptr
和std::unique_ptr
。 What are the equivalent smart pointers in Qt?Qt中的等效智能指针是什么?
Yes, there are QSharedPointer
and QScopedPointer
that work in similar way. 是的,有
QSharedPointer
和QScopedPointer
以类似的方式工作。 Some benefits for using these pointers include custom deleters , like QScopedPointerDeleter
, QScopedPointerArrayDeleter
, QScopedPointerPodDeleter
, QScopedPointerDeleteLater
. 使用这些指针的一些好处包括自定义删除器 ,如
QScopedPointerDeleter
, QScopedPointerArrayDeleter
, QScopedPointerPodDeleter
, QScopedPointerDeleteLater
。
Also you may use something like this, for QObject-based classes, if you need postponed deletion: 对于基于QObject的类,如果需要推迟删除,也可以使用类似的东西:
QSharedPointer<MyObject>(new MyObject, &QObject::deleteLater);
Attention: you should not use this smart pointer with direct setting of parent objects, because destructor will be called twice. 注意:您不应该使用此智能指针直接设置父对象,因为析构函数将被调用两次。
class Foo : public QObject
{
QScopedPointer<MyClass> p;
public:
Foo()
: p{ new MyClass(this) } // NO!!! Use new MyClass() without specifying parent
// Object lifetime is tracked by QScopedPointer
{}
};
If you have a list of raw pointers, you may use helpers like qDeleteAll()
for cleanup. 如果您有一个原始指针列表,您可以使用
qDeleteAll()
类的帮助qDeleteAll()
进行清理。 Example: 例:
QList<MyObject *> list;
//...
qDeleteAll( list );
list.clear();
3.
3。
4.
4。
5.
5。
It depends on your design and C++ code style. 这取决于您的设计和C ++代码风格。 Personal approach, from my experience:
个人方法,根据我的经验:
QWidget
-based classes. QWidget
的类使用parent-child。 Feel free to ask in comments for any clarifications. 如有任何澄清,请随时在评论中提问。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.