2017-08-25 85 views
4

除了this問題,我對文檔註解做了一些測試和研究。我的結論是,這種代碼應該沒有內存泄漏的工作:德爾福函數返回類對象

function testResultObject: TClassA; 
begin 
Result := TClassA.Create; 
Result.DoSomething; 
end; 

然後某處,我可以調用這個方法,從上述代碼:

var k: TClassA; 
begin 

k := testResultObject; 
try 
    //code code code 
finally 
    k.Free; 
end; 

end; 

由於雷米在它的最好的答案建議避免這種做事的方式,而改用testResultObject(x: TClassA): boolean之類的東西。在這種情況下,返回true/false可以告訴我是否一切正常,並且傳遞已創建的對象。

看看這段代碼:

function testResultObject: TClassA; 
begin 

Result := TClassA.Create; 

try 
    Result.DoSomething; 
except 
    Result.Free; 
end; 

end; 

與第一個版本以上的功能的問題是,DoSomething可能引發一個異常,如果是的話,我會泄漏內存。第二次執行try-except可以解決嗎?當然,我將不得不檢查結果是否被賦值,否則爲零。

我同意(如上所述)testResultObject(x: TClassA): boolean會更好。我只是想知道是否可以像我寫的那樣修復返回類功能的方式。

+2

就我個人而言,我一直使用第二種方法(真/假返回),它更好,使代碼更具可讀性。你的解決方案很好,但你應該在異常塊中添加一個結果:= nil –

+0

你誤解了我以前的答案。當您修改該對象時(在該示例中,將項目添加到列表中),傳遞對象作爲參數是很好的。這是一個完全不同的場景。 –

回答

9

您的代碼存在嚴重問題。如果發生錯誤,它會吞服該異常,並返回無效的對象引用。

這很容易解決。規範方式如下:

function testResultObject: TClassA; 
begin 
    Result := TClassA.Create;  
    try 
    Result.DoSomething; 
    except 
    Result.Free; 
    raise; 
    end; 
end; 

函數成功並返回一個新的對象。或者它失敗了,會自行清理並引發異常。

換句話說,這個函數的外觀和行爲就像構造函數一樣。你以相同的方式使用它:

obj := testResultObject; 
try 
    // do things with obj 
finally 
    obj.Free; 
end; 
+2

我認爲這是最好的答案。我喜歡用標準的方式來做事,在這裏我可以使用普通的試用版。此外,該功能正在做清理和代碼是很容易閱讀。 –

4

與此唯一的問題:

function testResultObject: TClassA; 
begin 
    Result := TClassA.Create;  
    try 
    Result.DoSomething; 
    except 
    Result.Free; 
    end; 
end; 

是,你有沒有辦法知道函數是否成功的方式。釋放對象不會改變引用;該變量仍將指向該對象過去存在的(現在)無效內存位置。如果您希望使用者能夠測試引用是否有效,則必須明確地將引用設置爲nil。如果你想使用這個模式(具有nil消費者測試),那麼你需要做的:

try 
    Result.DoSomething; 
except 
    FreeAndNil(Result); 
end; 

這樣調用者可以測試結果爲nil(使用Assigned或其他),你打算。但是,這仍然不是一個很乾淨的方法,因爲你仍然在吞嚥異常。另一種解決方案可能是簡單地引入一個新的構造函數或改變現有的構造函數。例如對於

TFoo = class 
    public 
    constructor Create(ADoSomething : boolean = false); 
    procedure DoSomething; 
end; 

constructor TClassA.Create(ADoSomething: Boolean = False); 
begin 
    inherited Create; 
    if ADoSomething then DoSomething; 
end; 

procedure TClassA.DoSomething; 
begin 
    // 
end; 

這樣你就可以擺脫所有的異常處理,只是稱這個爲:

function testResultObject: TClassA; 
begin 
    Result := TClassA.Create(true);  
end; 

既然你現在推DoSomething執行到構造任何異常,自然會自動調用析構函數,你的內存管理問題就會消失。其他答案也有很好的解決方案。

+0

我不明白爲什麼你會建議吞下這樣的例外 –

+0

@DavidHeffernan我不會建議它,這就是爲什麼我添加了一個更明智的選擇。 OP建議了這種模式,似乎表明他們很清楚它的缺點,所以我不覺得有必要說清楚這一點。 –

+0

我懷疑Raffaele是否讚賞這一點。在專注於泄漏的問題中沒有提及這一點。當我讀到它時,你的答案的前半部分會祝福異常吞嚥。這只是錯誤的。很容易修復。 –

5

你的第二種方法有效,但有2個嚴重問題。

  • 通過吞嚥所有異常(如J指出的),您將隱藏事情出錯的事實。
  • 沒有跡象表明調用者已經創建了調用者負責銷燬的對象。這使得使用該函數更容易出錯;並更容易造成內存泄漏。

我建議以下改進你的第二個方法:

  {Name has a clue that caller should take ownership of a new object returned} 
function CreateObjectA: TClassA; 
begin 
    {Once object is successfully created, internal resource protection is required: 
     - if no error, it is callers responsibility to destroy the returned object 
     - if error, caller must assume creation *failed* so must destroy object here 
    Also, by assigning Result of successful Create before *try*: 
     The object (reference) is returned 
      **if-and-only-if** 
     This function returns 'normally' (i.e. no exception state)} 
    Result := TClassA.Create;  
    try 
    Result.DoSomething; {that could fail} 
    except 
    {Cleanup only if something goes wrong: 
     caller should not be responsible for errors *within* this method} 
    Result.Free; 
    {Re-raise the exception to notify caller: 
     exception state means caller does not "receive" Result... 
     code jumps to next finally or except block} 
    raise; 
    end; 
end; 

上面創建功能的最重要的好處是:只要任何調用/客戶端代碼而言,它表現完全像正常的TObject.Create
正確使用模式是完全一樣的。

請注意,我並不熱衷於J的FreeAndNil建議,因爲如果調用代碼不檢查結果是否已分配:它很可能是AV。和代碼不正確檢查的結果將是一個有點亂:

var k: TClassA; 
begin 
    k := testResultObject; {assuming nil result on failed create, next/similar is *required*} 
    if Assigned(k) then {Note how this differs from normal try finally pattern} 
    try 
    //code using k 
    finally 
    k.Free; 
    end; 
end; 

注:需要注意的是,你不能永遠有你的來電者根本無視內存管理是非常重要的;這將我帶到下一部分。


所有上述之外,還有更使粗心的錯誤,如果你的testResultObject需要,你需要調用者根據需要創建和管理其生命週期的輸入對象的機會。 我不確定你爲什麼這麼反對這種方法? 如果不使用不同的內存模型,你不能變得比以下更簡單。

var k: TClassA; 
begin 
    k := TClassA.Create; 
    try 
    testResultObject(k); {Where this is simply implemented as k.DoSomething;} 
    //more code using k 
    finally 
    k.Free; 
    end; 
end; 
+0

正如我所說,我更喜歡像testResultObject(x:TClassA):布爾型,而不是返回TClassA。我只是想知道我的解決方案是否正確,沒有更多;)我同意你最後的實現是最好的! –

+1

@Raffaele你爲什麼喜歡布爾值。強制用戶測試它,併吞咽異常。有這樣一個簡單的方法來解決這個問題,並使你的調用代碼遵循標準模式。 –

+0

@Craig你的最終建議並不能解決函數在實例化對象之前可能引發異常的場景。這是你第一種方法獲勝的地方。 –