برنامه نویسی

[Qt] viii مدیریت حافظه هوشمند

نشانگرها موجودات ساده هستند. آنها دقیقاً مانند یون ها بودند: اگر آنها را در جایی قرار دهید ، آنها در جایی خواهند بود ، و این همان است.

من

من فکر می کنم اگر پیام های زیر را ببینید ناامید خواهید شد:

SIGSEGV

این ممکن است نتیجه نوعی خطاهای منطق اجرا عجیب باشد. نقل قول شروع C ++ 23 توسط آقای Ivor Horton و آقای Peter Van Weert در APPRASS ™ Publishers Werbatim (سلب مسئولیت قانونی: نه برای استفاده تجاری! فقط برای اهداف آموزشی و آموزشی) ،

نشانگر آویزان یک متغیر اشاره گر است که هنوز حاوی آدرس حافظه فروشگاه رایگان است که قبلاً توسط هر دو مورد استفاده شده است delete یا delete[]بشر با استفاده از یک نشانگر آویزان ، شما را می خوانید و یا اغلب بدتر می کنید ، به حافظه ای بنویسید که ممکن است از قبل توسط سایر قسمت های برنامه شما به آن اختصاص داده شود و در نتیجه انواع نتایج غیرقابل پیش بینی و غیر منتظره باشد.

اکنون ، من فرض می کنم که همه شما بچه ها با نشانگرهای آویزان و نشانگرهای تهی شکنجه شده اید ، درست است؟ استاندارد برخی از راه حل ها را ارائه می دهد ، مانند موارد و با استفاده از std::unique_ptr وت std::shared_ptr، و غیره ، اما (شخصاً) من واقعاً طراحی را دوست ندارم: آنها در تعاریف پیچیده هستند ، خطاهایی مانند Implicit template instantiation در همه جا ظهور می کنم ، و من وارد آن نیستم نام کلاس با هرگونه تأکید (این فقط یک سبک است ؛ اما ، مرد ، ما در حال انجام است C ++نه پاسکال، به شرط اینکه حتی پایتون به شما پیشنهاد می کند که نام کلاس را بدون زیرکرها انجام دهید)

مطمئناً شما را مقصر می دانید که به دلیل داشتن سریع ، من آن را با صد و بیست درصد می پذیرم. اما این من هستم من راضی نیستم من باید جایگزین بهتری پیدا کنم.

برای تسلط بر این موضوع ، ابتدا باید بدانیم که فلسفه C ++ در مورد تخصیص منابع یا حتی بهتر ، نحوه کار هر کامپایلر اصلی در زیر کاپوت چیست. بنابراین ، همانطور که توسط خالق C ++ ، Bjarne Stroustrup ، C ++ به عنوان اشیاء اختصاص داده شده است در سازندگان یک شی و آزاد کردن اشیاء در ویران کننده های چنین شی. این معمولاً به عنوان RAII شناخته می شود (دستیابی به منابع اولیه سازی است). با توجه به اینکه هیچ جمع کننده زباله در C ++ وجود ندارد که به طور خودکار اشیاء بلااستفاده را آزاد کند ، ما برنامه نویسان باید در بررسی خسته کننده نشت حافظه (یعنی به زبان انگلیسی ساده ، هدر رفتن حافظه) بسیار مراقب باشند. آیا راه حل بهتری وجود خواهد داشت؟

خوشبختانه ، Qt (در بسیاری از افراد و در اکثر اوقات) فقط به عنوان یک زندگی مشترک در آنجا ایستاده بود. توسعه دهندگان مهم کلاس هایی مانند QPointerبا QSharedPointerبا QWeakPointer، و غیره اجازه دهید اکنون در این مقاله به آنها شیرجه بزنیم.

اگر دلیل این موضوع را از من سؤال می کنید ، این است که من فقط در مقالات گذشته چندین بار به نشانگرها و مدیریت حافظه چارچوب QT اشاره کردم. آنها تا حدودی مهم هستند ، اما موضوعی که باید به عنوان خصوصیات پویا تحت پوشش قرار گیرند (که ویژگی های کشف نشده اما ذکر شده هنگام معرفی سیستم های متا-اوبات) بسیار مهم نیستند. با این حال ، به عنوان ادامه یادگیری سیستم متا اوبکت ، سرانجام فهمیدم که اگر نشانگرهای هوشمند را یاد بگیرید و یک تمرین خوب را در مورد آنها انجام دهید ، ترجیح می دهید.

قیاس نزدیک: std::auto_ptr

یک کلاس الگوی معمولی QPointer مسئول نظارت بر نشانگرهای QObject (و مشتقات آن ، البته). شما می توانید از آن به عنوان یک نشانگر کاملاً معمولی C/C ++ با استفاده از اپراتور استفاده کنید ->، و همچنین می توانید از طریق اطلاعات متا نشانگر دسترسی داشته باشید . اپراتور و روش هایی با نام های خود توضیحی مانند QPointer::clearبا QPointer::dataبا QPointer::swap وت QPointer::isNull، و غیره

مخصوصاً روش QPointer::isNull در صورت نظارت بررسی می کند QObject نمونه قابل استفاده است. تحت این شرایط ، روش باز خواهد گشت trueبشر در غیر این صورت ، باز خواهد گشت false و QPointerداده ها به طور خودکار خواهند بود 0بشر

جادو اتفاق می افتد وقتی QPointer عملگرها را برطرف می کنند *با /با -با >با !=، و غیره

عملی و کاربردهای

اگر یک نشانگر ابتدایی C ++ را به یک شیء از هر نوع نگه دارید و هنگامی که نشانگر حذف می شود (که به یک نشانگر خام تبدیل می شود) ، اگر به آن دسترسی پیدا کنید ، یک خطای گسل تقسیم بندی ایجاد می شود (همانطور که در تصویر بالا نشان داده شده است). در عوض ، شما فهمیدید که استفاده از آن راحت تر و کاربردی تر است QPointer کلاس (یا سایر کلاسهای مدیریت حافظه ساخته شده QT). در اینجا یک مثال کوچک وجود دارد:

    // ...
    QPointer<QLabel> label = new QLabel("Testing");
    // ... (may be destructive operations to the label object)
    if (label)
        label->show();
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در مثال بالا ، به یاد داشته باشید که بعد از آن ستاره اضافه نکنید QLabel Lexeme (مانند در QPointer، که اشتباه است و تولید می کند QLabl** کلاس).

همچنین ، label به طور خودکار به bool، که تقریباً شبیه به تعیین دسترسی یک نشانگر C ++ معمولی و ابتدایی است ، به جز اینکه مورد سابق از چکیده های QT استفاده می کند و از روش های باهوش تر استفاده می کند (برای مثال ، کد فوق به آن تبدیل می شود if ((!label.isNull()) && (label != 0))) ، در حالی که مورد دوم فقط بررسی می کند که آیا نشانگر برابر است 0x0 یا نه ، که ممکن است در بررسی نشانگرهای آویزان مؤثر نباشد.

قیاس نزدیک: std::shared_ptr

یک کلاس حتی دقیق تر به نام وجود دارد QSharedPointerبشر

در صورت استفاده از مرجع در داخل ، یعنی (برای مبتدیان) اگر شیء متعلق به آن باشد QSharedPointer حذف شد ، مشروط بر اینکه هیچ اشیاء دیگری به آن اشاره نکرده باشند (یعنی تعداد مرجع آن 0) ، سپس چنین شی (که توسط نشانگر اشاره شده بود) حذف شد.

هشدار: اگر یک اشاره گر به QSharedPointer، انجام دهید نه آن را به عنوان یک استدلال مستقیماً به عنوان QSharedPointer (به عنوان مثال با توجه به کد Obj*obj=new Obj; QSharedPointer objPtr(obj); void func(QSharedPointer&)، تماس بگیرید func(objPtr) به جای func(obj)). همچنین ، انجام دهید نه اشاره گر را در جای دیگر در کل پایگاه کد خود حذف کنید. انجام دادن نهبشر

به همین دلیل ، همانطور که ممکن است قبلاً فهمیدید ، این است که این امر باعث می شود سیستم شمارش مرجع ناله شود. با توجه به اینکه نمی تواند با استفاده از ابتدایی ، حذف نشانگرهای بدوی را کنترل کند delete اپراتور در جای دیگر ، سیستم شمارش مرجع از این رو حذف شده است وجود از چنین کد سپس ، دسترسی به آن ممکن است باعث شود SIGSEGV (همانطور که همه شما … خوب ، من هرگز نمک را به زخم نمی مالم).

در عوض ، شما می توانید از = اپراتور برای کپی کردن QSharedPointer به دیگری تایپ کنید ، و هر دو یک نشانگر یکسان بودند. این بار ، شمارش مرجع داخلی به درستی 1 افزایش می یابد ، و QT می تواند اطمینان دهد که شما از امنیت نشانگر هستید و هرگز با هیچ کس روبرو نمی شوید SIGSEGV (همانطور که شما … خوب ، خوب).

عملی و کاربردهای

اگر نمی خواهید به چه زمانی و کجا شیء شما حذف شود (در ضمن امیدوارید که حذف نشود) ، وت ویژگی جمع آوری زباله ها ، استفاده کنید QSharedPointerبشر در اینجا یک مثال ساده وجود دارد ، و من فقط به main.cpp پرونده با فقط:

#include 
#include 

class Debugger
{
public:
    Debugger()
    {
        qDebug() << "Debugger constructor called.";
        qDebug() << "";
    }

    ~Debugger()
    {
        qDebug() << "Debugger destructor called.";
        qDebug() << "";
    }

public:
    void debug(const QString &message)
    {
        qDebug() << "Debugger debugged called:";
        qDebug() << "    " << message;
    }
};

class ImportantObject
{
public:
    ImportantObject(QString name)
        : m_name(name)
    {}

    ~ImportantObject()
    {
        m_debugger->debug(QString("%1 exit").arg(m_name));
    }

public:
    void setDebugger(QSharedPointer<Debugger> &debugger)
    {
        m_debugger = debugger;
    }

private:
    QString m_name;
    QSharedPointer<Debugger> m_debugger;
};

int main(int argc, char *argv[])
{
    // Scopes:
    //     Think like functions, where the variables are not usable outside of it
    //     Sometimes called closures
    ImportantObject *obj1 = new ImportantObject("Important Object I");
    {
        QSharedPointer<Debugger> debugger(new Debugger);
        {
            QSharedPointer<Debugger> copiedDebugger(debugger);
        }

        obj1->setDebugger(debugger);

        {
            ImportantObject obj2("Important Object II");
            obj2.setDebugger(debugger);
        }
    }

    delete obj1;
    return 0;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

نتیجه اینجاست:

Debugger constructor called.

Debugger debugged called:
     "Important Object II exit"
Debugger debugged called:
     "Important Object I exit"
Debugger destructor called.
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بدیهی است که حافظه به طور خودکار مدیریت می شود.

قیاس نزدیک: std::weak_ptr

همه نمی خواهند ضعیف باشند ، به جز مدیریت حافظه. بوها اشاره گر ضعیف ضعیف است ، زیرا شما نمی توانید مستقیماً به اشاره گر دسترسی پیدا کنید (فقط از طریق QWeakPointer::data) ؛ دسترسی به نشانگر تضمین نشده است (یعنی شانس وجود دارد که نشانگر ارجاع شده قبلاً حذف شده باشد).

QWeakPointer تا حد زیادی یکسان بود QPointer، به جز این که فقط نمی تواند استفاده کند ، و باید با آن جفت شود QSharedPointerبشر همچنین ، اگرچه می تواند به یک معادل تبدیل شود QSharedPointer از طریق QWeakPointer::toStrongRef، خیلی ناخوشایند است که هنوز باید بررسی کنید که آیا به روزرسانی شده است QSharedPointer شیء تهی است یا نه.

اگر باشد این که مشکل ساز ، چرا برخی از ما هنوز تصمیم به استفاده از آن داریم؟

این تقریباً برابر با رابطه است std::string_view وت std::stringبشر در مورد قبلی ، شما نمی توانید رشته را تعیین ، اصلاح کنید ، پیچ و تاب کنید. شما می توانید تنها مشاهده آن با این حال ، برخی از ما هنوز تصمیم به استفاده از آن داریم زیرا این است سریعتر، زیرا فرآیندهای فهرست بندی و اصلاح تا حد زیادی حذف می شوند. یک قیاس مشابه بین a وجود دارد tuple و الف list در پایتون

عملی و کاربردهای

اگر فقط به یک ناظر برای دیدن اینکه آیا شیء تهی است یا خیر ، به جای انجام کاری به آن.

بنابراین ، در اینجا یک مثال اصلاح شده از مثال بالا ، گنجانیده شده است QSharedPointer وت QWeakPointer:

#include 
#include 

class Debugger
{
public:
    Debugger()
    {
        qDebug() << "Debugger constructor called.";
        qDebug() << "";
    }

    ~Debugger()
    {
        qDebug() << "Debugger destructor called.";
        qDebug() << "";
    }

public:
    void debug(const QString &message)
    {
        qDebug() << "Debugger debugged called:";
        qDebug() << "    " << message;
        qDebug() << "";
    }
};

class ImportantObject
{
public:
    ImportantObject(QString name)
        : m_name(name)
    {}

    ~ImportantObject()
    {
        m_debugger->debug(QString("%1 exit").arg(m_name));
    }

public:
    void setDebugger(QSharedPointer<Debugger> &debugger)
    {
        m_debugger = debugger;
    }

private:
    QString m_name;
    QSharedPointer<Debugger> m_debugger;
};

int main(int argc, char *argv[])
{
    // Scopes:
    //     Think like functions, where the variables are not usable outside of it
    //     Sometimes called closures
    ImportantObject *obj1 = new ImportantObject("Important Object I");
    {
        QSharedPointer<Debugger> debugger(new Debugger);
        QWeakPointer<Debugger> weakReference(debugger);

        if (!weakReference.isNull())
            qDebug() << "At I: Weak reference is not null\n";

        {
            QSharedPointer<Debugger> copiedDebugger(debugger);
            weakReference = copiedDebugger;
        }

        obj1->setDebugger(debugger);

        {
            ImportantObject obj2("Important Object II");
            obj2.setDebugger(debugger);
        }

        if (!weakReference.isNull())
            qDebug() << "At II: Weak reference is not null\n";
    }

    delete obj1;
    return 0;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

نتیجه اینجاست:

Debugger constructor called.

At I: Weak reference is not null

Debugger debugged called:
     "Important Object II exit"

At II: Weak reference is not null

Debugger debugged called:
     "Important Object I exit"

Debugger destructor called.
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

قیاس نزدیک: std::unique_ptr

QScopedPointer از اپراتور استفاده می کند new برای اختصاص پویا اشیاء روی پشته ، و اطمینان می دهد که در هر زمان اشیاء پویا ایجاد شده با موفقیت می توانند حذف شوند.

اگر مالکیت یک شیء خاص را به آن بدهید QScopedPointer، بازیابی آن امکان پذیر نیست. همچنین ، هرگز مالکیت را به هیچ شیء دیگر یا نشانگر هوشمند منتقل نمی کند.

توجه داشته باشید که حتی می توانید روش حذف را در طول ساخت و ساز اختصاص دهید.

چهار یاری تخصیص داخلی ارائه شده توسط QScopedPointer، یعنی:

  1. QScopedPointerDeleter کاربردهای delete وت new برای تخصیص اشیاء ؛
  2. QScopedPointerArrayDeleter کاربردهای delete[] وت new[] برای تخصیص اشیاء ؛
  3. QScopedPointerPodDeleter (کجا Pod به معنای “داده های قدیمی ساده”) از C استفاده می کند (به همین دلیل است ساده وت قدیمی) free وت malloc برای تخصیص اشیاء ؛ وت
  4. QScopedPointerDeleteLater کاربردهای QObject::deleteLater برای حذف عاقلانه اشیاء. توجه داشته باشید که (احتمالاً) این خطای را در مورد نشانگرهای scoped غیرQObject-دین.

آنها می توانند مانند (از کتابچه راهنمای رسمی QT) استفاده شوند:

QScopedPointer<int, QScopedPointerArrayDeleter<int> > arrayPointer(new int[42]);

QScopedPointer<int, QScopedPointerPodDeleter> podPointer(reinterpret_cast<int *>(malloc(42)));
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

من گفتم ساخته شده، زیرا چارچوب QT توانا حتی به شما امکان می دهد یاران تخصیص خود را ایجاد کنید:

struct ScopedPointerCustomDeleter
{
    static inline void cleanup(MyCustomClass *pointer)
    {
        myCustomDeallocator(pointer);
    }
};

QScopedPointer<MyCustomClass, ScopedPointerCustomDeleter> customPointer(new MyCustomClass);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

… جایی که وجود دارد باید یک روش باشد static public inline void cleanup(TargetClass *pointer) (هر چند جاوا نیست!)

عملی و استفاده

اگر اظهارات کنترل زیادی داشتید که بسیار پیچیده بودند ، ممکن است بخواهید از آن استفاده کنید QScopedPointer:

int function(Enum enum)
{
    QScopedPointer<Class> myClass = new Class;
    switch (enum)
    {
    case 1:
        if (function1(myClass))
        {
            if (externalProcess(myClass))
                return finalProcess(myClass);
            else if (anotherProcess(myClass))
                return 42;
            else if (someFunction(myClass))
                return -1;
            errorProcess(myClass);
            return 0;
        }

        else
        {
            return (yetAnother(myClass) ? 1 : 0);
        }
        break;
    case 2:
        externalProcess(myClass); break;
    default:
        return yetAnotherProcessOtherThanThose(myClass);
    }
    errorProcess(myClass);
    return -42;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

اگر استفاده نمی کنید QScopedPointer، این یک آشفتگی خواهد بود.

QObject گاهی اوقات به عنوان یک نشانگر نیمه هوشمند در نظر گرفته می شود. به این دلیل است که QObject مدیریت جزئی حافظه را نشان می دهد ، اما به سختی به عنوان روشهای مدیریت قدرتمند از آن نشان داده شده است QScopedPointer یا QSharedPointer، و غیره

شما قبلاً بسیاری را دیده اید QObject فرزندان دارای پارامتر زیر در سازندگان خود هستند:

Constructor(QObject *parent = nullptr)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

به عنوان ضمیمه “سلام ، جهان!” به عنوان مثال ، بگذارید اکنون توضیح دهم که چرا تنظیم شیء والدین مالکیت خود را به والدین خود منتقل می کند ، و همچنین وقتی والدین نابود شدند ، نیز نابود شد.

در اینجا نابود کننده کامل کلاس پایه است:

/*!
    Destroys the object, deleting all its child objects.

    All signals to and from the object are automatically disconnected, and
    any pending posted events for the object are removed from the event
    queue. However, it is often safer to use deleteLater() rather than
    deleting a QObject subclass directly.

    \warning All child objects are deleted. If any of these objects
    are on the stack or global, sooner or later your program will
    crash. We do not recommend holding pointers to child objects from
    outside the parent. If you still do, the destroyed() signal gives
    you an opportunity to detect when an object is destroyed.

    \warning Deleting a QObject while it is handling an event
    delivered to it can cause a crash. You must not delete the QObject
    directly if it exists in a different thread than the one currently
    executing. Use deleteLater() instead, which will cause the event
    loop to delete the object after all pending events have been
    delivered to it.

    \sa deleteLater()
*/

QObject::~QObject()
{
    Q_D(QObject);
    d->wasDeleted = true;
    d->blockSig = 0; // unblock signals so we always emit destroyed()

    if (!d->bindingStorage.isValid()) {
        // this might be the case after an incomplete thread-move
        // remove this object from the pending list in that case
        if (QThread *ownThread = thread()) {
            auto *privThread = static_cast<QThreadPrivate *>(
                        QObjectPrivate::get(ownThread));
            privThread->removeObjectWithPendingBindingStatusChange(this);
        }
    }

    // If we reached this point, we need to clear the binding data
    // as the corresponding properties are no longer useful
    d->clearBindingStorage();

    QtSharedPointer::ExternalRefCountData *sharedRefcount = d->sharedRefcount.loadRelaxed();
    if (sharedRefcount) {
        if (sharedRefcount->strongref.loadRelaxed() > 0) {
            qWarning("QObject: shared QObject was deleted directly. The program is malformed and may crash.");
            // but continue deleting, it's too late to stop anyway
        }

        // indicate to all QWeakPointers that this QObject has now been deleted
        sharedRefcount->strongref.storeRelaxed(0);
        if (!sharedRefcount->weakref.deref())
            delete sharedRefcount;
    }

    if (!d->wasWidget && d->isSignalConnected(0)) {
        emit destroyed(this);
    }

    if (!d->isDeletingChildren && d->declarativeData && QAbstractDeclarativeData::destroyed)
        QAbstractDeclarativeData::destroyed(d->declarativeData, this);

    QObjectPrivate::ConnectionData *cd = d->connections.loadAcquire();
    if (cd) {
        if (cd->currentSender) {
            cd->currentSender->receiverDeleted();
            cd->currentSender = nullptr;
        }

        QBasicMutex *signalSlotMutex = signalSlotLock(this);
        QMutexLocker locker(signalSlotMutex);

        // disconnect all receivers
        int receiverCount = cd->signalVectorCount();
        for (int signal = -1; signal < receiverCount; ++signal) {
            QObjectPrivate::ConnectionList &connectionList = cd->connectionsForSignal(signal);

            while (QObjectPrivate::Connection *c = connectionList.first.loadRelaxed()) {
                Q_ASSERT(c->receiver.loadAcquire());

                QBasicMutex *m = signalSlotLock(c->receiver.loadRelaxed());
                bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
                if (c == connectionList.first.loadAcquire() && c->receiver.loadAcquire()) {
                    cd->removeConnection(c);
                    Q_ASSERT(connectionList.first.loadRelaxed() != c);
                }
                if (needToUnlock)
                    m->unlock();
            }
        }

        /* Disconnect all senders:
         */
        while (QObjectPrivate::Connection *node = cd->senders) {
            Q_ASSERT(node->receiver.loadAcquire());
            QObject *sender = node->sender;
            // Send disconnectNotify before removing the connection from sender's connection list.
            // This ensures any eventual destructor of sender will block on getting receiver's lock
            // and not finish until we release it.
            sender->disconnectNotify(QMetaObjectPrivate::signal(sender->metaObject(), node->signal_index));
            QBasicMutex *m = signalSlotLock(sender);
            bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m);
            //the node has maybe been removed while the mutex was unlocked in relock?
            if (node != cd->senders) {
                // We hold the wrong mutex
                Q_ASSERT(needToUnlock);
                m->unlock();
                continue;
            }

            QObjectPrivate::ConnectionData *senderData = sender->d_func()->connections.loadRelaxed();
            Q_ASSERT(senderData);

            QtPrivate::QSlotObjectBase *slotObj = nullptr;
            if (node->isSlotObject) {
                slotObj = node->slotObj;
                node->isSlotObject = false;
            }

            senderData->removeConnection(node);
            /*
              When we unlock, another thread has the chance to delete/modify sender data.
              Thus we need to call cleanOrphanedConnections before unlocking. We use the
              variant of the function which assumes that the lock is already held to avoid
              a deadlock.
              We need to hold m, the sender lock. Considering that we might execute arbitrary user
              code, we should already release the signalSlotMutex here – unless they are the same.
            */
            const bool locksAreTheSame = signalSlotMutex == m;
            if (!locksAreTheSame)
                locker.unlock();
            senderData->cleanOrphanedConnections(
                        sender,
                        QObjectPrivate::ConnectionData::AlreadyLockedAndTemporarilyReleasingLock
                        );
            if (needToUnlock)
                m->unlock();

            if (locksAreTheSame) // otherwise already unlocked
                locker.unlock();
            if (slotObj)
                slotObj->destroyIfLastRef();
            locker.relock();
        }

        // invalidate all connections on the object and make sure
        // activate() will skip them
        cd->currentConnectionId.storeRelaxed(0);
    }
    if (cd && !cd->ref.deref())
        delete cd;
    d->connections.storeRelaxed(nullptr);

    if (!d->children.isEmpty())
        d->deleteChildren();

    if (Q_UNLIKELY(qtHookData[QHooks::RemoveQObject]))
        reinterpret_cast<QHooks::RemoveQObjectCallback>(qtHookData[QHooks::RemoveQObject])(this);

    Q_TRACE(QObject_dtor, this);

    if (d->parent)        // remove it from parent object
        d->setParent_helper(nullptr);
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

به زبان انگلیسی ساده ، پنج مرحله مهم وجود دارد:

  1. کارآزمایی QWeakPointer منابع ضعیف ؛
  2. سیگنال را منتشر کنید QObject::destroyed؛
  3. همه اتصالات شکاف سیگنال ایجاد شده را قطع کنید.
  4. کودکان را حذف کنید وت
  5. خود را از شیء والدین خارج کنید.

بنابراین ، اگر ما یک کلاس داشته باشیم که به طور خودکار می تواند چنین فرایندهایی را مدیریت کند ، بسیار راحت تر خواهد بود. کلاس باید به همان اندازه نظارت کند QObject همانطور که ممکن است درست مثل QPointer، و این می تواند مدیریت حافظه خودکار را دقیقاً مانند نشان دهد QScopedPointerبشر

معرفی: QObjectCleanupHandlerبشر

شما می توانید استفاده کنید QObjectCleanupHandler::add برای افزودن شی ، isEmpty برای تعیین اینکه آیا هنوز زندگی وجود دارد QObjectو همچنین می توانید همه اشیاء را با استفاده از آن پاک کنید QObjectCleanupHandler::clearبشر

عملی و استفاده

من به طور معمول این کلاس را به عنوان مدیر منبع می سازم و پیشنهاد می کنم این کار را نیز انجام دهید. اگر چندین نفر دارید QObjectشما تقریباً یکسان هستند ، شما استفاده می کنید QObjectCleanupHandlerبشر من شخصاً فکر می کنم این یکی مفید است.

در اینجا یک مثال مینیمالیستی وجود دارد:

void SomethingsManager::run()
{
    Manager *m1 = new Manager(this);
    // ...
    // ...
    QObjectCleanupHandler cleanup;
    cleanup.add(m1);
    cleanup.add(m2);
    // ...

    exec();
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

استراحت کنید و به برخی از اسناد C ++ مراجعه کنید تا مفهوم آن را درک کنید std::unique_ptr وت std::shared_ptr، و غیره

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا