正确复制Delphi对象的方法

gab*_*abr 21 delphi constructor copy-constructor

使用构造函数或实例函数复制对象实例的优缺点是什么?

例A:

type
  TMyObject = class
  strict private
    FField: integer; 
  public
    constructor Create(srcObj: TMyObject); overload; 
    //alternatively:
    //constructor CreateFrom(srcObj: TMyObject);
    property Field: integer read FField;
  end;

constructor TMyObject.Create(srcObj: TMyObject);
begin
  inherited Create;
  FField := srcObj.Field;
end;
Run Code Online (Sandbox Code Playgroud)

例B:

type
  TMyObject = class
  strict private
    FField: integer; 
  public
    function Clone: TMyObject;
    property Field: integer read FField;
  end;

function TMyObject.Clone: TMyObject;
begin
  Result := TMyObject.Create;
  Result.FField := FField;
end;
Run Code Online (Sandbox Code Playgroud)

一个主要的区别立即浮出水面 - 在后一种情况下,Create构造函数必须是虚拟的,以便可以基于TMyObject构建支持Clone的类层次结构.

假设这不是问题 - TMyObject和基于它的所有内容完全在我的控制之下.在Delphi中进行复制构造函数的首选方法是什么?您觉得哪个版本更具可读性?你什么时候使用前者或后者?讨论.:)

编辑:我对第一个例子的主要关注是,与第二种方法相比,使用率非常高,即

newObj := TMyObject.Create(oldObj)
Run Code Online (Sandbox Code Playgroud)

newObj := oldObj.Clone;
Run Code Online (Sandbox Code Playgroud)

EDIT2或"我为什么要单行操作"

我同意在大多数情况下,Assign是一种合理的方法.通过简单地使用assign在内部实现"复制构造函数"甚至是合理的.

我通常在多线程并通过消息队列传递对象时创建这样的副本.如果对象创建速度很快,我通常会传递原始对象的副本,因为这样可以真正简化对象所有权问题.

我喜欢写作

Send(TMyObject.Create(obj));
Run Code Online (Sandbox Code Playgroud)

要么

Send(obj.Clone);
Run Code Online (Sandbox Code Playgroud)

newObj := TMyObject.Create;
newObj.Assign(obj);
Send(newObj);
Run Code Online (Sandbox Code Playgroud)

Mar*_*ort 29

第一个添加有关要创建哪个对象的信息,第二个不添加.这可以用于实例化例如类的后代或祖先

Delphi方式(TPersistent)分离创建和克隆:

dest := TSomeClass.Create; 
dest.Assign(source);  
Run Code Online (Sandbox Code Playgroud)

并且具有您明确选择要实例化的类的相同属性.但是您不需要两个构造函数,一个用于正常使用,另一个用于要克隆.

由于oneline要求编辑 你当然可以使用Delphi元类混合它(未经测试)

type
  TBaseSomeObject = class;
  TBaseObjectClass = class of TBaseSomeObject;

  TBaseSomeObject = class(TPersistent)
    function Clone(t: TBaseObjectClass = nil): TBaseSomeObject; virtual;
  end;

...

  function TBaseSomeObject.Clone(t: TBaseObjectClass = nil): TBaseSomeObject;
  begin
    if Assigned(t) then
      Result := t.Create
    else
      Result := TBaseObjectClass(Self.ClassType).Create;
    Result.Assign(Self);
  end;


 SendObject(obj.Clone); // full clone.
 SendObject(obj.Clone(TDescandantObject)); // Cloned into Descendant object 
Run Code Online (Sandbox Code Playgroud)

其余的,只需实现您的assign()操作符,您可以混合多种方式.

EDIT2

我用D2009中测试的代码替换了上面的代码.这些类型的某些依赖关系可能会让您感到困惑,希望通过这种方式更加清晰.当然,你必须研究分配机制.我也测试了metaclass=nil默认参数,它可以工作,所以我添加了它.

  • 好吧,我确实希望有一天达到6666 :-) (3认同)
  • 我会投票支持你的答案,但这会破坏你的 5,555 分。;-) 恭喜! (2认同)

Too*_*the 6

我认为没有一种正确的方式只取决于个人风格.(正如马可指出的那样,还有更多方法.)

  • 构造函数方式很短但它违反了构造函数只能构造对象的原则.这可能不是问题.
  • 虽然您需要为每个类提供调用,但克隆方式很短.
  • 分配方式更像Delphi.它分离了创建和初始化,这是很好的,因为我们喜欢一种方法,一种功能概念,使代码更好地维护.

如果使用流实现Assign,则只有一个地方需要担心哪些字段需要可用.