क्या ऑब्जेक्ट की कक्षा लागू होने वाले इंटरफ़ेस का कॉन्स्ट इंटरफ़ेस पैरामीटर वाले किसी विधि के ऑब्जेक्ट का नया उदाहरण पास करते समय संकलक संकेत/चेतावनी देना चाहिए?क्या कंपाइलर इन्सटेंस इंस्टेंस को सीधे कॉन्फ़िगर इंटरफ़ेस पैरामीटर के रूप में पास करते समय संकेत/चेतावनी देना चाहिए?
संपादित करें: इस मुद्दे को स्पष्ट करने के लिए पाठ्यक्रम का नमूना सरल है। लेकिन वास्तविक जीवन में यह और अधिक जटिल हो जाता है: क्या होगा यदि निर्माण और उपयोग कोड में है जो अलग है (विभिन्न इकाइयां, विभिन्न वर्ग, विभिन्न परियोजनाएं)? क्या होगा यदि यह विभिन्न लोगों द्वारा बनाए रखा जाता है? क्या होगा यदि एक गैर-कॉन्स्ट पैरामीटर एक कॉन्स बन जाता है, और सभी कॉलिंग कोड की जांच नहीं की जा सकती है (क्योंकि कोड बदलने वाले व्यक्ति को सभी कॉलिंग कोड तक पहुंच नहीं है)?
क्रैश नीचे की तरह कोड, और कारण खोजने के लिए बहुत मुश्किल है।
पहले लॉग:
1.Run begin
1.RunLeakCrash
2.RunLeakCrash begin
NewInstance 1
AfterConstruction 0
3.LeakCrash begin
_AddRef 1
4.Dump begin
4.Dump Reference=10394576
4.Dump end
_Release 0
_Release Destroy
BeforeDestruction 0
3.LeakCrash Reference got destroyed if it had a RefCount of 1 upon entry, so now it can be unsafe to access it
_AddRef 1
4.Dump begin
4.Dump Reference=10394576
4.Dump end
_Release 0
_Release Destroy
BeforeDestruction 0
3.LeakCrash end with exception
1.Run end
EInvalidPointer: Invalid pointer operation
तो कोड है कि समय से पहले ही वस्तु उदाहरण एक अंतरफलक को लागू करने विज्ञप्ति:
//{$define all}
program InterfaceConstParmetersAndPrematureFreeingProject;
{$APPTYPE CONSOLE}
uses
SysUtils,
Windows,
MyInterfacedObjectUnit in '..\src\MyInterfacedObjectUnit.pas';
procedure Dump(Reference: IInterface);
begin
Writeln(' 4.Dump begin');
Writeln(' 4.Dump Reference=', Integer(PChar(Reference)));
Writeln(' 4.Dump end');
end;
procedure LeakCrash(const Reference: IInterface);
begin
Writeln(' 3.LeakCrash begin');
try
Dump(Reference); // now we leak because the caller does not keep a reference to us
Writeln(' 3.LeakCrash Reference got destroyed if it had a RefCount of 1 upon entry, so now it can be unsafe to access it');
Dump(Reference); // we might crash here
except
begin
Writeln(' 3.LeakCrash end with exception');
raise;
end;
end;
Writeln(' 3.LeakCrash end');
end;
procedure RunLeakCrash;
begin
Writeln(' 2.RunLeakCrash begin');
LeakCrash(TMyInterfacedObject.Create());
Writeln(' 2.RunLeakCrash end');
end;
procedure Run();
begin
try
Writeln('1.Run begin');
Writeln('');
Writeln('1.RunLeakCrash');
RunLeakCrash();
finally
Writeln('');
Writeln('1.Run end');
end;
end;
begin
try
Run();
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
EInvalidPointer ही Dump(Reference);
को दूसरी कॉल के अंदर प्रकट होगा। कारण यह है कि संदर्भ प्रकट करने वाली अंतर्निहित वस्तु की संदर्भ संख्या पहले ही शून्य है, इसलिए अंतर्निहित वस्तु पहले ही नष्ट हो चुकी है।
डाला जाता है या संकलक द्वारा छोड़े गए संदर्भ गिनती कोड पर कुछ नोट:
- मापदंडों
const
(procedure Dump(Reference: IInterface);
में की तरह) के साथ चिह्नित नहीं अंतर्निहित ट्राई/अंत में ब्लॉक पाने के संदर्भ गिनती करने के लिए। - मापदंडों
const
(procedure LeakCrash(const Reference: IInterface);
में की तरह) के साथ चिह्नित एक वस्तु उदाहरण सृष्टि के परिणाम गुजर किसी भी संदर्भ गिनती कोड - नहीं मिलता है (जैसे
LeakCrash(TMyInterfacedObject.Create());
) किसी भी संदर्भ गिनती कोड
अकेला के सभी उत्पन्न नहीं करता है उपरोक्त कंपाइलर व्यवहार बहुत तार्किक हैं, लेकिन संयुक्त वे एक EInvalidPointer का कारण बन सकते हैं।
EInvalidPointer केवल एक बहुत संकीर्ण उपयोग पैटर्न में प्रकट होता है।
पैटर्न को संकलक द्वारा पहचानना आसान है, लेकिन जब आप इसमें फंस जाते हैं तो डीबग करना या कारण ढूंढना बहुत मुश्किल है।
कामकाज बहुत सरल है: TMyInterfacedObject.Create()
का परिणाम मध्यवर्ती चर में, फिर इसे LeakCrash()
पर पास करें।
क्या संकलक इस उपयोग पैटर्न के बारे में आपको संकेत या चेतावनी देना चाहिए?
अंत में कोड मैं पता लगाने के लिए उपयोग किए गए सभी _AddRef/_Release/आदि कॉल:
unit MyInterfacedObjectUnit;
interface
type
// Adpoted copy of TInterfacedObject for debugging
TMyInterfacedObject = class(TObject, IInterface)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
class function NewInstance: TObject; override;
property RefCount: Integer read FRefCount;
end;
implementation
uses
Windows;
procedure TMyInterfacedObject.AfterConstruction;
begin
InterlockedDecrement(FRefCount);
Writeln(' AfterConstruction ', FRefCount);
end;
procedure TMyInterfacedObject.BeforeDestruction;
begin
Writeln(' BeforeDestruction ', FRefCount);
if RefCount <> 0 then
System.Error(reInvalidPtr);
end;
class function TMyInterfacedObject.NewInstance: TObject;
begin
Result := inherited NewInstance;
TMyInterfacedObject(Result).FRefCount := 1;
Writeln(' NewInstance ', TMyInterfacedObject(Result).FRefCount);
end;
function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Writeln(' QueryInterface ', FRefCount);
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TMyInterfacedObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
Writeln(' _AddRef ', FRefCount);
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
Writeln(' _Release ', FRefCount);
if Result = 0 then
begin
Writeln(' _Release Destroy');
Destroy;
end;
end;
end.
--jeroen
ठीक है; यह क्यूसी होगा। –
मैं अब 10 वर्षों से इस पर काम कर रहा हूं। मुझे विश्वास नहीं है कि यह मुद्दा पहले से ही ज्ञात नहीं था और इसलिए माना जाता है कि यह डिजाइन/ठीक नहीं होगा। आज इसके बारे में सोचते हुए यह स्पष्ट प्रतीत होता है कि इसे ठीक किया जा सकता है क्योंकि यह अन्य प्रबंधित प्रकारों (स्ट्रिंग्स, डायन एरे, वेरिएंट इत्यादि) के लिए नहीं होता है। –
@ बैरी: http://qc.embarcadero.com/wc/qcmain। एएसपीएक्स? डी = 90482 –