【问题标题】:Class/Static Constants in DelphiDelphi中的类/静态常量
【发布时间】:2010-09-09 11:06:54
【问题描述】:

在 Delphi 中,我希望能够创建一个与类关联的私有对象,并从该类的所有实例中访问它。在 Java 中,我会使用:

public class MyObject {
    private static final MySharedObject mySharedObjectInstance = new MySharedObject();
}

或者,如果 MySharedObject 需要更复杂的初始化,我可以在 Java 中实例化并在静态初始化块中初始化它。

(你可能已经猜到了……我知道我的 Java,但我对 Delphi 还很陌生……)

无论如何,我不想在每次创建 MyObject 实例时都实例化一个新的 MySharedObject,但我确实希望从 MyObject 的每个实例都可以访问一个 MySharedObject。 (实际上是日志记录促使我尝试解决这个问题 - 我正在使用 Log4D,并且我想将 TLogLogger 作为每个具有日志记录功能的类的类变量存储。)

在 Delphi 中做这种事情的最简洁的方法是什么?

【问题讨论】:

  • 请注意,类变量在 Delphi 7 之前不可用。

标签: delphi


【解决方案1】:

下面是我将如何使用类变量、类过程和初始化块:

unit MyObject;

interface

type

TMyObject = class
   private
     class var FLogger : TLogLogger;
   public
     class procedure SetLogger(value:TLogLogger);
     class procedure FreeLogger;
   end;

implementation

class procedure TMyObject.SetLogger(value:TLogLogger);
begin
  // sanity checks here
  FLogger := Value;
end;

class procedure TMyObject.FreeLogger;
begin
  if assigned(FLogger) then 
    FLogger.Free;
end;

initialization
  TMyObject.SetLogger(TLogLogger.Create);
finalization
  TMyObject.FreeLogger;
end.

【讨论】:

  • 如果您需要私有字段,则需要 SetLogger 过程。我同意最终确定程序的评论。
  • 我认为应该可以从初始化程序中设置一个私有字段,因为可以从单元内访问私有字段。它必须是“严格私有的”才能仅在类内访问。
  • 你不需要在释放之前调用assigned - free 会为你做这件事。
  • 你也可以使用类构造函数:docwiki.embarcadero.com/RADStudio/Seattle/en/… 而不是初始化/终结
【解决方案2】:

去年,Hallvard Vassbotn 写了一篇关于我为此制作的 Delphi hack 的博客,这篇文章分为两部分:

  1. Hack#17: Virtual class variables, Part I
  2. Hack#17: Virtual class variables, Part II

是的,读起来很长,但很有收获。

总而言之,我已将名为 vmtAutoTable 的(已弃用的)VMT 条目用作变量。 VMT 中的这个槽可以用来存储任何 4 字节的值,但是如果你想存储,你总是可以分配一个包含所有你想要的字段的记录。

【讨论】:

【解决方案3】:
 TMyObject = class
    private
      class var FLogger : TLogLogger;
      procedure SetLogger(value:TLogLogger);
      property Logger : TLogLogger read FLogger write SetLogger;
    end;

procedure TMyObject.SetLogger(value:TLogLogger);
begin
  // sanity checks here
  FLogger := Value;
end;

请注意,此类变量可从任何类实例写入,因此您可以在代码中的其他位置设置它,通常基于某些条件(记录器的类型等)。

编辑:该类的所有后代也将相同。在其中一个孩子中更改它,它会为所有后代实例更改。 您还可以设置默认实例处理。

 TMyObject = class
    private
      class var FLogger : TLogLogger;
      procedure SetLogger(value:TLogLogger);
      function GetLogger:TLogLogger;
      property Logger : TLogLogger read GetLogger write SetLogger;
    end;

function TMyObject.GetLogger:TLogLogger;
begin
  if not Assigned(FLogger)
   then FLogger := TSomeLogLoggerClass.Create;
  Result := FLogger;
end;

procedure TMyObject.SetLogger(value:TLogLogger);
begin
  // sanity checks here
  FLogger := Value;
end;

【讨论】:

  • 这可能与线程安全有关吗?多线程是否会导致 TMyObject 的实例尝试使用尚未完全初始化的 FLogger?
  • 理论上是的。添加一个 crit.section 会有所帮助 - 但这是有代价的。最好的方法是类方法,例如在 init 部分示例中,与 get 例程中的断言相结合。您甚至可能希望在 setter 中使用断言来避免使用多个 setter。 YMMV。
【解决方案4】:

您要查找的关键字是“class var”——这会在您的类声明中启动一个类变量块。如果您希望在其后包含其他字段,则需要以“var”结束块(否则该块可能以“private”、“public”、“procedure”等说明符结束)。比如

(编辑:我重新阅读了该问题并将引用计数移至 TMyClass - 因为您可能无法编辑要共享的 TMySharedObjectClass 类,如果它来自其他人的库)

  TMyClass = class(TObject)
  strict private
    class var
      FMySharedObjectRefCount: integer;
      FMySharedObject: TMySharedObjectClass;
    var
    FOtherNonClassField1: integer;
    function GetMySharedObject: TMySharedObjectClass;
  public
    constructor Create;
    destructor Destroy; override;
    property MySharedObject: TMySharedObjectClass read GetMySharedObject;
  end;


{ TMyClass }
constructor TMyClass.Create;
begin
  if not Assigned(FMySharedObject) then
    FMySharedObject := TMySharedObjectClass.Create;
  Inc(FMySharedObjectRefCount);
end;

destructor TMyClass.Destroy;
begin
  Dec(FMySharedObjectRefCount);
  if (FMySharedObjectRefCount < 1) then
    FreeAndNil(FMySharedObject);

  inherited;
end;

function TMyClass.GetMySharedObject: TMySharedObjectClass;
begin
  Result := FMySharedObject;
end;

请注意以上不是线程安全的,并且可能有更好的引用计数方法(例如使用接口),但这是一个简单的示例,应该可以帮助您入门。注意 TMySharedObjectClass 可以替换为 TLogLogger 或任何你喜欢的。

【讨论】:

  • 我可以看到,如果您在类外部共享对象,这样的事情可能是有意义的,但是对于仅在类实例之间共享的对象,它有很多脚手架代码。
【解决方案5】:

嗯,这不是美,但在 Delphi 7 中运行良好:

TMyObject = class
pulic
    class function MySharedObject: TMySharedObject; // I'm lazy so it will be read only
end;

implementation

...

class function MySharedObject: TMySharedObject;
{$J+} const MySharedObjectInstance: TMySharedObject = nil; {$J-} // {$J+} Makes the consts writable
begin
    // any conditional initialization ...
   if (not Assigned(MySharedObjectInstance)) then
       MySharedObjectInstance = TMySharedOject.Create(...);
  Result := MySharedObjectInstance;
end;

我目前正在使用它来构建单例对象。

【讨论】:

    【解决方案6】:

    对于我想做的事情(一个私有类常量),我能想出的最简洁的解决方案(基于迄今为止的响应)是:

    unit MyObject;
    
    interface
    
    type
    
    TMyObject = class
    private
      class var FLogger: TLogLogger;
    end;
    
    implementation
    
    initialization
      TMyObject.FLogger:= TLogLogger.GetLogger(TMyObject);
    finalization
      // You'd typically want to free the class objects in the finalization block, but
      // TLogLoggers are actually managed by Log4D.
    
    end.
    

    也许更面向对象会是这样的:

    unit MyObject;
    
    interface
    
    type
    
    TMyObject = class
    strict private
      class var FLogger: TLogLogger;
    private
      class procedure InitClass;
      class procedure FreeClass;
    end;
    
    implementation
    
    class procedure TMyObject.InitClass;
    begin
      FLogger:= TLogLogger.GetLogger(TMyObject);
    end;
    
    class procedure TMyObject.FreeClass;
    begin
      // Nothing to do here for a TLogLogger - it's freed by Log4D.
    end;
    
    initialization
      TMyObject.InitClass;
    finalization
      TMyObject.FreeClass;
    
    end.
    

    如果有多个这样的类常量,这可能更有意义。

    【讨论】:

    • 在 init 部分放东西有一些缺点:即使你不使用类,只要你使用单元,它就会被链接进来。此外,初始化顺序可能并不总是您认为的那样,并且会随着您更改 uses 子句中单位的位置而改变。
    • 呃,听起来有点难看!是否可以在其单元的初始化部分运行之前创建一个类的实例?换句话说,TMyObject 的实例是否可以在初始化部分中设置之前尝试使用 FLogger?
    • @MB: 不,这是不可能的——在创建任何 TMyObject 甚至使用类方法之前,定义 TMyObject 的单元将被包含在内,并且此时初始化部分运行。在单元的初始化部分执行之前,您不能在任何单元中使用该类
    【解决方案7】:

    我认为在你想出一个“完美”的解决方案之前需要回答两个问题..

    • 首先,TLogLogger 是否是线程安全的。可以从多个线程调用相同的 TLogLogger 而不调用“同步”吗?即使是这样,以下内容仍可能适用
    • 类变量是线程范围内的还是真正的全局变量?
    • 如果类变量是真正的全局变量,并且 TLogLogger 不是线程安全的,则最好使用单元全局线程变量来存储 TLogLogger(尽管我不喜欢以任何形式使用“全局”变量) ,例如

    代码:

    interface
    type
      TMyObject = class(TObject)
      private
        FLogger: TLogLogger; //NB: pointer to shared threadvar
      public
        constructor Create;
      end;
    implementation
    threadvar threadGlobalLogger: TLogLogger = nil;
    constructor TMyObject.Create;
    begin
      if not Assigned(threadGlobalLogger) then
        threadGlobalLogger := TLogLogger.GetLogger(TMyObject); //NB: No need to reference count or explicitly free, as it's freed by Log4D
      FLogger := threadGlobalLogger;
    end;
    

    编辑:似乎类变量是全局存储的,而不是每个线程的实例。详情请见this question

    【讨论】:

    • threadvar - 很酷,我不知道你可以在 Delphi 中做到这一点。我一直假设类变量只会比实例变量更有意义如果对象(例如TLogLogger)是线程安全的。但看起来 threadvar 提供了一个介于两者之间的选项。
    • 我仍然想知道类变量是全局变量还是范围内的线程。我的猜测是全球性的,但也许我应该添加一个新问题......
    【解决方案8】:

    在 Delphi 中,静态变量被实现为 变量类型常量 :)

    这可能有点误导。

    procedure TForm1.Button1Click(Sender: TObject) ;
    const
       clicks : Integer = 1; //not a true constant
    begin
      Form1.Caption := IntToStr(clicks) ;
      clicks := clicks + 1;
    end;
    

    是的,另一种可能性是在模块的implementation 部分中使用全局变量。

    这仅在编译器开关“Assignable Consts”已全局打开或使用{$J+} 语法 (tnx Lars) 时有效。

    【讨论】:

    • 这只有在编译器开关AssignableConsts打开时才有效
    【解决方案9】:

    在版本 7 之前,Delphi 没有静态变量,您必须使用全局变量。

    为了使其尽可能私密,请将其放在您单位的 implementation 部分。

    【讨论】:

    • 在 Delphi 7 之前都是如此。更高版本有类变量(请参阅其他答案)。
    猜你喜欢
    • 2014-12-25
    • 1970-01-01
    • 1970-01-01
    • 2015-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多