我需要在我的控制下爲有限數量的類撤銷+重做堆棧,必須非常非常快,並且使用RTTI和XML或流是不可行的,因爲實例的數量可能高達2000+嵌套對象列表。對象需要通過備忘錄模式複製進出,並立即重新加載。我可以通過複製它的內存克隆一個對象嗎?
有沒有辦法通過複製內存並重新實例化內存中的對象來克隆對象?
我需要在我的控制下爲有限數量的類撤銷+重做堆棧,必須非常非常快,並且使用RTTI和XML或流是不可行的,因爲實例的數量可能高達2000+嵌套對象列表。對象需要通過備忘錄模式複製進出,並立即重新加載。我可以通過複製它的內存克隆一個對象嗎?
有沒有辦法通過複製內存並重新實例化內存中的對象來克隆對象?
幾乎沒有。您可以輕鬆地複製對象的內存,但該內存的一部分將成爲指針,在這種情況下您只複製該引用。這些指針也可以包含字符串和其他對象。
我認爲最好的方法是從TPersistent(或任何decendant)繼承這些類,併爲它們中的每一個實現Assign方法。這樣,您可以創建第二個實例並將該對象分配給該新實例。在Assign實現中,您可以自己決定應該複製哪些數據,哪些不是。
一個簡單的方法克隆對象是:
見紀念品設計模式,在這裏例如: http://sourcemaking.com/design_patterns/memento/delphi/1
使用TMemoryStream可以實現撤銷/重做 - 只有當對象的數據保存並流式傳輸時,纔會出現重做/撤銷。它使用對象的saveToStream/loadfromstream機制,並允許正確重建引用。
更新:
TMyObject = class
procedure SaveToStream(AStream : TStream);
procedure LoadFromStream(AStream : TStream);
end;
然後:
變更前:
myobject.SaveToStream(MemoryStream);
undoManager.AddItem(myobject.Identifier, MemoryStream);
Do change object...
通過恢復
myobject := FindObject(undomanager.LastItem.Identifier)
myobject.LoadFromStream(undomanager.LastItem.MemoryStream);
我用類似的方法對我的CAD軟件,它的工作原理相當好。它也可用於存儲多個對象,在一個操作中進行更改。
TObject沒有SaveToStream。 –
我不是說TObject,我知道它沒有持久性功能。 – George
那麼你的意思是什麼?請編輯您的答案,以便更加精確。你在句子中間放大* Object *,表示你的意思是'TObject'。如果你的意思不是專有名詞,你還需要在它前面有一篇文章(即*或* *)。 –
2000+嵌套對象不是很大,即使使用RTTI(磁盤訪問或壓縮會慢得多),速度也不會太慢。使用直接的SaveToStream手動序列化,如果使用FastMM4(Delphi 2006以後的默認內存管理器),則速度非常快。
也許你可能會改變你的算法,並使用動態數組代替(有一個開源串行器here)。但是你的數據可能不適合這種記錄。
或者從不/很少釋放內存,只使用對象或記錄引用。你可以擁有一個內存對象池,帶有某種手動垃圾收集器,並且只處理對象(或記錄)的引用。
如果您在對象內部有string
,您可能不會重新分配它們並保留已使用字符串的全局列表:引用計數和寫時複製將使其比標準序列化和分配(即使使用FastMM4)快得多。
在任何情況下,都應該對您的應用程序進行真實的分析。一般人對性能瓶頸的猜測大多數時間都是錯誤的。只有相信一個探查器和一個掛鐘。也許你現在的實現並不是那麼慢,真正的瓶頸不是對象進程,而是其他地方。
不要優化得太早。 「在你做得更快之前把它做好,在你做得更快之前要說清楚,當你做得更快時保持正確。」 - Kernighan和Plauger,編程風格的元素。
對Kernighan和Plauger報價+1。 –
感謝您的洞察力。使用克隆和分配方法確實直接依賴內存管理器,因爲您指出的內存管理器速度非常快 - 關鍵部分是關注內存管理器,並通過選擇性填充克隆並將克隆標記爲克隆,以智能方式對其進行管理內部並避免RTTI。 – MX4399
@ MX4399它也值得嘗試全局'string'列表:使用引用計數可能明顯比流方法更快。當然,'Assign()'方法將使用完全相同的引用計數機制的優點。 –
我以前用於這種情況的一種方法是用我需要保留的對象的一部分聲明一個記錄,並在類中使用該記錄。以Optimizing Class Size in Delphi. Is there something like "packed classes"?爲例說明我的舊回答。
由於記錄在賦值時被複制,因此很容易將字段從一個對象類型複製到另一個。
例如
TMyFields = record
ID: Integer;
Name: string;
end;
TMyClass = class(TPersistent)
private
FFields: TMyFields;
FStack: TStack;
class TStackItem = class(TObject)
public
Fields: TMyFields;
end;
protected
property ID: integer read FFields.ID;
property Name: string read FFields.Name;
procedure Push;
// etc...
end;
procedure TMyClass.Push;
var
NewItem: TStackItem;
begin
NewItem := TStackItem.Create;
NewItem.Fields := FFields;
FStack.Push(NewItem);
end;
當然,因爲您使用的是XE,您可以使用Generics.Collections.TObjectStack代替。
非常有趣的方法 – MX4399
因此,如果不克隆對象,您無法實現記憶模式?你所有的物體是否都從TPersistent繼承,因此支持TPersistent.Assign是你最好的選擇? –
只要你的對象不包含引用,你可以做到這一點。 –
問題是,對象比它的記憶更多。那麼打開文件,其他句柄,同步原語,與refcounting接口等?只有對象本身知道如何正確地複製自己。 – haimg