2015-08-26 6 views
7

मेरे पास एक परिदृश्य है जहां एक अज्ञात QObject सिग्नल उत्सर्जित करके एसिंक्रोनस ऑपरेशन शुरू करता है। प्राप्त करने वाला स्लॉट QObject-सूचक को संग्रहीत करता है और बाद में इस ऑब्जेक्ट की एक संपत्ति सेट करता है। वस्तु इस बीच हो सकती है।क्या यह जांचने का कोई तरीका है कि QObject-सूचक अभी भी Qt में मान्य है या नहीं?

तो, क्या यह सूचक अभी भी वैध है या नहीं यह जांचने का एक सुरक्षित तरीका है?

पीएस .: मुझे QObject::destroyed सिग्नल के बारे में पता है, जिसे मैं उस पॉइंटर के setProperty पर कॉल करने के लिए ऑब्जेक्ट से कनेक्ट कर सकता हूं। लेकिन मुझे आश्चर्य है, अगर यह आसान काम करता है।

+4

क्या [QPointer'] (http://doc.qt.io/qt-5/qpointer.html) काम करेगा? – thuga

+1

मैंने अपना जवाब अपडेट कर लिया है, 'QObject :: नष्ट' आपकी मदद नहीं करेगा। –

+0

@ थुगा हां, यह वास्तव में उत्तर है। बहुत देर से इसे स्वीकार करने के लिए खेद है। हालांकि, क्यूबा ओबेर ने बताया, यह केवल उसी धागे में काम करता है। –

उत्तर

10

यह एक अच्छा सवाल है, लेकिन यह गलत सवाल है।

क्या यह जांचने का कोई तरीका है कि सूचक वैध है या नहीं? हाँ। QPointer विशेष रूप से ऐसा करने के लिए डिज़ाइन किया गया है।

लेकिन इस प्रश्न का उत्तर बेकार है यदि वस्तु किसी अन्य धागे में रहता है! आप केवल यह जानते हैं कि एक ही बिंदु पर पर मान्य है - उत्तर तुरंत बाद में मान्य नहीं है।

अनुपस्थित अन्य तंत्र, QPointer किसी ऑब्जेक्ट को किसी भिन्न थ्रेड में रखने के लिए बेकार है - यह आपकी सहायता नहीं करेगा। क्यों?इस परिदृश्य को देखो:

   Thread A     Thread B 
1. QPointer returns a non-zero pointer 
2.          deletes the object 
3. Use the now-dangling pointer 

मैं QObject :: नष्ट संकेत है, जो मैं उद्देश्य यह है कि सूचक की setProperty फोन करने वाले से कनेक्ट कर सकता है के बारे में पता कर रहा हूँ। लेकिन मुझे आश्चर्य है, अगर यह आसान काम करता है। एक सूत्र के भीतर, या धागा सीमाओं के पार -

destroyed संकेत बेकार जब पंक्तिबद्ध कनेक्शन का उपयोग करके भेजा है। वे प्रत्यक्ष कनेक्शन का उपयोग कर, एक थ्रेड के भीतर उपयोग किए जाने वाले हैं।

समय लक्ष्य थ्रेड घटना पाश स्लॉट कॉल ऊपर उठाता करके, उद्भव वस्तु लंबे समय तक चला गया है। इससे भी बदतर - यह हमेशा एक एकल थ्रेडेड एप्लिकेशन में मामला है। समस्या का कारण QPointer जैसा है: destroyed सिग्नल इंगित करता है कि ऑब्जेक्ट अब मान्य नहीं है, लेकिन इसका मतलब यह नहीं है कि यह सिग्नल प्राप्त करने से पहले मान्य था जब तक कि आप सीधे कनेक्शन का उपयोग कर रहे हैं (और एक ही धागे में हैं) या आप उपयोग कर रहे एक अवरुद्ध कनेक्शन भेज दिए जाएंगे।

अवरुद्ध का उपयोग करते हुए कनेक्शन पंक्तिबद्ध, जब तक async धागा ऑब्जेक्ट के विलोपन प्रतिक्रिया खत्म का अनुरोध वस्तु की धागा रोकेंगे। हालांकि यह निश्चित रूप से "काम करता है", यह दो धागे को स्पैस उपलब्धता के साथ संसाधन पर सिंक्रनाइज़ करने के लिए मजबूर करता है - एसिंक थ्रेड के ईवेंट लूप में सामने वाला स्थान। हां, यह सचमुच आप प्रतिस्पर्धा करते हैं - एक कतार में एक ही स्थान जो मनमाने ढंग से लंबा हो सकता है। हालांकि यह डिबगिंग के लिए ठीक हो सकता है, लेकिन उत्पादन कोड में इसका कोई स्थान नहीं है जब तक कि सिंक्रनाइज़ करने के लिए थ्रेड को ब्लॉक करना ठीक न हो।

आप इस तथ्य के आस-पास बहुत मेहनत करने की कोशिश कर रहे हैं कि आप थ्रेड के बीच QObject पॉइंटर पास कर रहे हैं, और ऑब्जेक्ट के जीवनकाल को प्राप्त करने वाले थ्रेड के दृष्टिकोण से, अनियंत्रित है। यह तुम्हारी समस्या है। आप कच्चे ऑब्जेक्ट पॉइंटर को पास करने वाले द्वारा सब कुछ हल करेंगे। इसके बजाए, आप एक साझा स्मार्ट पॉइंटर पास कर सकते हैं, या सिग्नल-स्लॉट कनेक्शन का उपयोग कर सकते हैं: जब भी कनेक्शन का अंत नष्ट हो जाता है तो वे गायब हो जाते हैं। यही वही है जो आप चाहते हैं।

वास्तव में, क्यूटी के स्वयं के डिजाइन पैटर्न इस में संकेत। QNetworkReply न केवल क्योंकि यह एक QIODevice है, लेकिन क्योंकि यह धागा सीमाओं के पार समाप्त अनुरोधों का सीधा संकेत का समर्थन करने के लिए एक होना चाहिए QObject है। अनुरोधों की एक भीड़ के प्रकाश में, QNetworkAccessManager::finished(QNetworkReply*) से कनेक्ट करने से समयपूर्व निराशा हो सकती है। आपकी वस्तु को संभावित रूप से बहुत बड़ी संख्या में उत्तरों की अधिसूचना मिलती है, लेकिन यह वास्तव में केवल उनमें से एक या बहुत कम में रुचि रखती है। इस प्रकार अनुरोधकर्ता को सीधे सूचित करने का एक तरीका होना चाहिए कि उसका एकमात्र अनुरोध किया गया हो - और यही QNetworkReply::finished है।

तो, आगे बढ़ने के लिए एक आसान तरीका Request एक done संकेत के साथ एक QObject हो बनाने के लिए है। जब आप अनुरोध तैयार करते हैं, तो अनुरोध करने वाले ऑब्जेक्ट को उस सिग्नल से कनेक्ट करें। तुम भी एक functor कनेक्ट कर सकते हैं, लेकिन लगता है कि functor का अनुरोध वस्तु के संदर्भ में कार्यान्वित करते हैं:

// CORRECT 
connect(request, &Request::done, requester, [...](...){...}); 
// WRONG 
connect(request, &Request::done, [...](...){...}); 
नीचे

दर्शाता है कि यह कैसे एक साथ रखा जा सकता है। अनुरोधों का जीवनकाल साझा (संदर्भ-गणना) स्मार्ट सूचक के उपयोग के माध्यम से प्रबंधित किया जाता है। यह जीवन को बल्कि आसान बनाता है। हम जांचते हैं कि main रिटर्न पर कोई अनुरोध मौजूद नहीं है।

#include <QtCore> 

class Request; 
typedef QSharedPointer<Request> RequestPtr; 
class Request : public QObject { 
    Q_OBJECT 
public: 
    static QAtomicInt m_count; 
    Request() { m_count.ref(); } 
    ~Request() { m_count.deref(); } 
    int taxIncrease; 
    Q_SIGNAL void done(RequestPtr); 
}; 
Q_DECLARE_METATYPE(RequestPtr) 
QAtomicInt Request::m_count(0); 

class Requester : public QObject { 
    Q_OBJECT 
    Q_PROPERTY (int catTax READ catTax WRITE setCatTax NOTIFY catTaxChanged) 
    int m_catTax; 
public: 
    Requester(QObject * parent = 0) : QObject(parent), m_catTax(0) {} 
    Q_SLOT int catTax() const { return m_catTax; } 
    Q_SLOT void setCatTax(int t) { 
     if (t != m_catTax) { 
     m_catTax = t; 
     emit catTaxChanged(t); 
     } 
    } 
    Q_SIGNAL void catTaxChanged(int); 
    Q_SIGNAL void hasRequest(RequestPtr); 
    void sendNewRequest() { 
     RequestPtr req(new Request); 
     req->taxIncrease = 5; 
     connect(req.data(), &Request::done, this, [this, req]{ 
     setCatTax(catTax() + req->taxIncrease); 
     qDebug() << objectName() << "has cat tax" << catTax(); 
     QCoreApplication::quit(); 
     }); 
     emit hasRequest(req); 
    } 
}; 

class Processor : public QObject { 
    Q_OBJECT 
public: 
    Q_SLOT void process(RequestPtr req) { 
     QThread::msleep(50); // Pretend to do some work. 
     req->taxIncrease --; // Figure we don't need so many cats after all... 
     emit req->done(req); 
     emit done(req); 
    } 
    Q_SIGNAL void done(RequestPtr); 
}; 

struct Thread : public QThread { ~Thread() { quit(); wait(); } }; 

int main(int argc, char ** argv) { 
    struct C { ~C() { Q_ASSERT(Request::m_count == 0); } } check; 
    QCoreApplication app(argc, argv); 
    qRegisterMetaType<RequestPtr>(); 
    Processor processor; 
    Thread thread; 
    processor.moveToThread(&thread); 
    thread.start(); 

    Requester requester1; 
    requester1.setObjectName("requester1"); 
    QObject::connect(&requester1, &Requester::hasRequest, &processor, &Processor::process); 
    requester1.sendNewRequest(); 
    { 
     Requester requester2; 
     requester2.setObjectName("requester2"); 
     QObject::connect(&requester2, &Requester::hasRequest, &processor, &Processor::process); 
     requester2.sendNewRequest(); 
    } // requester2 is destructed here 
    return app.exec(); 
} 

#include "main.moc" 
+0

बहुत धीरज और दबाव के साथ QPointer को इंगित करने के लिए धन्यवाद। अंततः मुझे इसे देखने के लिए मजबूर किया गया :-)। ऐसा लगता है कि मुझे लगता है कि क्यूटी में एक बहुत ही आवश्यक वर्ग गायब है। यह वास्तव में मुझे चाहिए जो - मुझे नष्ट करने के लिए सुनता है और पॉइंटर को 0 पर सेट करता है। वैश्विक QObject * सूची के लिए कोई आवश्यकता नहीं है। स्वच्छ क्यूटी रास्ता। बहु-थ्रेडिंग मेरे मामले में कोई मुद्दा नहीं है, क्योंकि यह सब जीयूआई-थ्रेड में किया जाता है। धन्यवाद! अगर इल्या ने अपना जवाब हटा दिया, तो मैं तुम्हारा चयन करूंगा। –

+0

ओह, मुझे पता नहीं था, कि चयनित उत्तर बदला जा सकता है। ठीक है, तुम्हारा विजेता :-) –

+0

@ValentinHeinitz सुनिश्चित करें कि दोनों 'QPointer' और वस्तु यह अंक केवल एक ही धागे से इस्तेमाल कर रहे हैं करने के लिए सुनिश्चित है। इस तरह इसका उपयोग किया जाना है, और यह पूरी तरह से काम करेगा। जैसे ही ऑब्जेक्ट और पॉइंटर का उपयोग विभिन्न धागे में किया जाता है, आपके पास अपरिभाषित व्यवहार होता है और आपको अन्य समाधानों की तलाश करनी चाहिए। –

6

यह जांचना असंभव है कि सूचक अभी भी मान्य है। इसलिए, यहां एकमात्र सुरक्षित तरीका है कि QObject (और मल्टीथ्रेडिंग मामले में) को हटाने के बारे में जानकारी प्राप्त करना है: ऑब्जेक्ट तक पहुंचने से पहले आपको यह सुनिश्चित करने के लिए जांचने और अवरोधित करने की आवश्यकता है कि ऑब्जेक्ट को किसी अन्य थ्रेड में हटाया नहीं जाएगा चेक)। इसका कारण सरल है:

  • सैद्धांतिक रूप से यह संभव है कि प्रारंभिक ऑब्जेक्ट को हटाने के बाद, सिस्टम उस स्मृति में एक और ऑब्जेक्ट रखेगा (इसलिए पॉइंटर वैध जैसा दिखेगा)।
  • या यह संभव है कि ऑब्जेक्ट हटा दिया जाएगा, लेकिन इसकी याददाश्त किसी अन्य चीज़ द्वारा ओवरराइट नहीं की जाएगी, इसलिए यह अभी भी वैध की तरह दिखाई देगी (लेकिन यह वास्तव में यह अमान्य होगी)।
  • तो, पता लगाने का कोई तरीका नहीं है कि पॉइंटर मान्य है, अगर आपके पास केवल पॉइंटर है। आपको कुछ और चाहिए

यह भी सुरक्षित नहीं है बस बहु सूत्रण मामले में वस्तु के हटाने के बारे में एक संकेत भेजते हैं (या के रूप में आप का सुझाव दिया QObject::destroyed उपयोग करने के लिए) के लिए। क्यूं कर? क्योंकि यह संभव है, कि चीजें इस क्रम में होता है:,

  • QObject एक संदेश भेजने के लिए "एक बजे हटाया जाने वाला"
  • QObject नष्ट कर दिया,
  • अपने प्राप्त कोड है कि सूचक का उपयोग करता है (और यह है गलत और खतरनाक),
  • आपके प्राप्त कोड को "मुझे हटाया जा रहा है" संदेश (बहुत देर हो चुकी है) प्राप्त होता है।

तो, केवल एक धागे के मामले में आपको QPointer की आवश्यकता है। अन्यथा आपको QSharedPointer या QWeakPointer (दोनों थ्रेड-सुरक्षित हैं) की आवश्यकता है - क्यूबा ओबेर का उत्तर देखें।

+0

पहला बिंदु वास्तव में अच्छा है! पिछले 2 के बारे में, मेरा मतलब क्यूटी द्वारा प्रदान किया गया एक सुरक्षित तरीका है, सादा सी ++ नहीं। मुझे लगता है कि क्यूटी में आंतरिक रूप से संग्रहीत सभी वस्तुओं की एक सूची होनी चाहिए। तो इस सूची में चेक आसान उत्तर सस्ती होना चाहिए। साझा पॉइंटर पैटर्न की तरह कॉल-बैक में मदद करेगा, लेकिन मैं सिग्नल/स्लॉट का उपयोग केवल करना चाहता हूं। –

+0

मुझे लगता है, बिंदु 1 प्रश्न का उत्तर देता है। यह उचित रूप से काम नहीं कर सकता है। यहां तक ​​कि यह एक पॉइंटर एक मान्य QObject को इंगित करता है, आप कभी नहीं जानते, यह आपके ऑब्जेक्ट का मतलब है। धन्यवाद! –

+0

यह बहुत भ्रामक है। 'QPointer' का उपयोग थ्रेड में उपयोग करने के लिए कभी नहीं किया गया है! यह एपीआई को इस तरह इस्तेमाल करने के लिए आवश्यक नहीं प्रदान करता है। ** aargh **। –

संबंधित मुद्दे

 संबंधित मुद्दे