【问题标题】:Does the construction of an object through a class variable always need the parent class to have a constructor?通过类变量构造对象总是需要父类有构造函数吗?
【发布时间】:2017-08-08 11:41:52
【问题描述】:

我知道这是一个令人费解的问题,我相信有人可以将其简化为基本问题。

考虑以下代码:

TTestClass = class
public
end;

TTestClassDescendant = class(TTestClass)
public
  constructor Create;
end;


implementation

procedure TForm1.Button1Click(Sender: TObject);
var tc: TTestClass;
begin
  tc := TTestClassDescendant.Create;
  tc.Free;
end;

{ TTestClassDescendant }

constructor TTestClassDescendant.Create;
begin
  ShowMessage('Create executed')  // this gets executed
end;

Create 过程被正确执行。

现在考虑以下代码:

TTestClass = class
public
end;

TTestClassDescendant = class(TTestClass)
public
  constructor Create;
end;

TTestClassClass = class of TTestClass;

implementation

procedure TForm1.Button1Click(Sender: TObject);
var tc: TTestClass;
    tcc: TTestClassClass;
begin
  tcc := TTestClassDescendant;
  tc := tcc.Create;
  tc.Free
end;

{ TTestClassDescendant }

constructor TTestClassDescendant.Create;
begin
  ShowMessage('Create executed')  // this does NOT get executed
end;

后代类的 Create 过程不再执行。

但是,如果我在父类中引入构造函数并在后代类中覆盖它,它确实会被执行:

TTestClass = class
public
  constructor Create; virtual;
end;

TTestClassDescendant = class(TTestClass)
public
  constructor Create; override;
end;

如果我忽略了显而易见的事情,请原谅我,但不应该在通过类变量进行构造时执行第二个代码块中的构造函数代码,就像通过类标识符本身调用它时一样?

【问题讨论】:

    标签: class delphi constructor delphi-xe2


    【解决方案1】:

    如果我忽略了显而易见的事情,请原谅我,但不应该 当第二个代码块中的构造函数代码被执行时 构造是通过一个类变量发生的,就像它是 通过类标识符本身调用?

    不,不应该。

    声明是

    TTestClassClass = class of TTestClass; // note: of TTestClass!
    

    这就是为什么基 TTestClass(它继承自 TObject)的(空)构造函数被调用的原因,因为那是 声明的TTestClassClass 指的是。

    如果你想调用 actual 构造函数,那么你应该在基类中使构造函数 virtual覆盖 在后代中,就像您在问题的最后一部分中所做的那样。


    FWIW,如果你声明一个

    TTestClassDescendantClass = class of TTestClassDescendant;
    

    然后用它来实例化一个后代类,那么你确实应该得到一个TTestClassDescendant 并且构造函数应该显示你所期望的。

    类比

    但是构造函数的这种行为就像其他非虚拟和虚拟方法一样:

    type
      TBase = class
        procedure DoSomething; // outputs: "TBase: Doing something"
      end;
    
      TDesc = class(TBase)
        procedure DoSomething; // outputs: "Descendant does it now"
      end;
    
    var
      D: TBase;
    begin
      D := TDesc.Create;
      D.DoSomething;
    

    由于D 被声明为TBase,对D.DoSomething 的调用将调用TBase.DoSomething,而不是TDesc.DoSomething

    但如果DoSomethingvirtual 并且在TDesc覆盖,那么将使用D 中的actual 类。你给出的例子是一样的,除了你在那里使用元类。

    【讨论】:

    • 我希望它的行为方式与 tc := TTestClassDescendant.Create 完全相同,因为我所做的只是将 TTestClassDescendant 替换为 tcc,它是一个包含 TTestClassDescendant 的变量。它的行为方式不同,这非常违反直觉。
    • 并非真的违反直觉。除非方法是虚拟的或动态的,否则方法解析发生在编译时。
    • 如果我有 tc := TTestClassDescendant.Create 调用正确的过程和 tcc := TTestClassDescendant; tc := tcc.Create 不调用它,这很违反直觉,不是吗? tcc 只不过是 TTestClassDescendant。它们应该是相同的。
    • Okidoki。我认为这可能与stackoverflow.com/questions/791069/… 重复
    • 我想补充一点,如果出于某种原因不能选择声明虚拟构造函数,那么在TObject 中定义的虚拟AfterConstruction 方法随时可供您覆盖,只是为了在这种情况下,您希望后代类在构造时执行某些操作。
    猜你喜欢
    • 2014-12-16
    • 1970-01-01
    • 2018-01-10
    • 2013-10-25
    • 2014-04-23
    • 1970-01-01
    • 2019-12-05
    • 1970-01-01
    • 2011-02-24
    相关资源
    最近更新 更多