简体   繁体   English

Qt:如何使用Qt的Smartpointers

[英]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_ptrstd::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 ++具有严格的对象销毁顺序,并且保证objectsother之前被销毁。 Thus, by the time other.~QObject() runs, there are no MyObject children, and thus there's no issue with double-deletion. 因此,当other.~QObject()运行时,没有MyObjectMyObject ,因此双删除没有问题。

In general, here are the viable approaches to storing QObject collections, with their requirements: 通常,以下是存储QObject集合的可行方法,以及它们的要求:

  1. std::array - all elements of same type, fixed size, cannot be returned std::array - 所有相同类型的元素,固定大小,不能返回

  2. std::list - all elements of same type, doesn't have a RandomAccessIterator , container owns the objects std::list - 所有相同类型的元素,没有RandomAccessIterator ,容器拥有对象

  3. 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 (当然可以清除/销毁),容器拥有对象

  4. 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>> - 任何类型的元素,即它是一个多态容器,容器拥有对象

  5. 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*>

  6. 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: 现在,回答你的问题:

  1. 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. 你也可以自由地拥有无父对象。

  2. 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_ptrstd::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,因此必须支持这些指针。

  3. 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. 也许您需要一个实际上需要使用智能指针的不同代码段。

  4. Should I change function signature into using smart pointers? 我应该将功能签名更改为使用智能指针吗? No. You haven't motivated any smart pointer use. 不,你没有动机使用任何智能指针。

  5. 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. QSharedPointerQScopedPointer ,它有点类似于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 . lsmi都可以是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或类似的东西。

  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. 据我了解,如果我从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: 但也有一些例外:

  • You should be careful with objects that are created on the stack . 您应该小心在堆栈上创建的对象。
  • 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. 访问此指针的性能与原始指针相同。

  1. In C++ I can get by with std::shared_ptr and std::unique_ptr . 在C ++中,我可以使用std::shared_ptrstd::unique_ptr What are the equivalent smart pointers in Qt? Qt中的等效智能指针是什么?

Yes, there are QSharedPointer and QScopedPointer that work in similar way. 是的,有QSharedPointerQScopedPointer以类似的方式工作。 Some benefits for using these pointers include custom deleters , like QScopedPointerDeleter , QScopedPointerArrayDeleter , QScopedPointerPodDeleter , QScopedPointerDeleteLater . 使用这些指针的一些好处包括自定义删除器 ,如QScopedPointerDeleterQScopedPointerArrayDeleterQScopedPointerPodDeleterQScopedPointerDeleteLater

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: 个人方法,根据我的经验:

  1. Use parent-child only for QWidget -based classes. 仅对基于QWidget的类使用parent-child。
  2. Use shared pointers only when you can't control an object lifetime 仅在无法控制对象生存期时才使用共享指针
  3. In all other cases - use unique pointers 在所有其他情况下 - 使用唯一指针
  4. To share something between two classes - use references . 要在两个类之间共享 - 使用引用
  5. In case of complex logic - use smart pointers. 在复杂逻辑的情况下 - 使用智能指针。 But double-check for loops (use weak pointers in most cases) 但是仔细检查循环(在大多数情况下使用弱指针)

Feel free to ask in comments for any clarifications. 如有任何澄清,请随时在评论中提问。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM