Take a look at this class:
TTest = class(TObject)
public
constructor Create(A:Integer);overload;
constructor Create(A,B:Integer);overload;
end;
Now when we want to use the class:
var
test: TTest;
begin
test:= TTest.Create; //this constructor is still visible and usable!
end;
Can anyone help me with hiding this constructor?
So long as you have overloaded constructors named Create, you cannot hide the parameterless TObject constructor when deriving from TObject.
This is discussed here: http://www.yanniel.info/2011/08/hide-tobject-create-constructor-delphi.html
If you are prepared to put another class between your class and TObject you can use Andy Hausladen's trick:
TNoParameterlessContructorObject = class(TObject)
strict private
constructor Create;
end;
TTest = class(TNoParameterlessContructorObject)
public
constructor Create(A:Integer);overload;
constructor Create(A,B:Integer);overload;
end;
You can hide the inherited Create by just introducing a non overloaded Create. As you need two overloaded Create, you can either merge those into one Create with an optional second parameter:
TTest = class(TObject)
public
constructor Create(A:Integer; B: Integer = 0);
end;
This will give a compiler warning, signalling that you're hiding the default parameterless constructor. To get rid of the warning you can declare the hiding constructor like so:
TTest = class(TObject)
public
constructor Create(A:Integer; B: Integer = 0); reintroduce;
end;
or, if this is not feasible, you can introduce an intermediate class introducing the first create and then the final class with the overloaded second one:
preTest = class(TObject)
public
constructor Create(A:Integer); reintroduce;
end;
TTest = class(preTest)
public
constructor Create(A,B:Integer);overload;
end;
Another option is to use the deprecated keyword and raise an exception at runtime.
TTest = class(TObject)
public
constructor Create; overload; deprecated 'Parameterless constructor is not Supported for a TTest class';
constructor Create(const A: Integer); overload;
constructor Create(const A, B: Integer); overload;
end;
implementation
constructor TTest.Create;
begin
raise Exception.Create('Parameterless constructor is not Supported for a TTest class.');
end;
Through the two inheritance, user creation of TMySingleton class can be prevented from design time rather than runtime.
unit MySingleton;
interface
uses System.Classes, System.SysUtils;
type
// Constructor Block external access
THideConstructor = class abstract
strict protected
constructor Create; virtual; abstract;
end;
// Switching the access to the Create function THideConstructor in TObject through the constructor Overloading
// Declaring Create Method as a procedure to prevent class call-TMySingle.Create('string') call impossible
TOverloadConstructor = class(THideConstructor)
public
procedure Create(s: string); reintroduce; overload; deprecated 'null method';
end;
TMySingleton = class sealed(TOverloadConstructor)
private
class var MyObj: TMySingleton;
strict protected
// Hiding TOverloadConstructor.Create(s: string);
// Implement THideConstructor.Create
constructor Create; override;
public
class function Obj: TMySingleton;
function Echo(const value: string): String;
destructor Destroy; override;
end;
implementation
{ TMySingleton }
constructor TMySingleton.Create;
begin
// TODO
end;
destructor TMySingleton.Destroy;
begin
Self.MyObj := nil;
inherited;
end;
function TMySingleton.Echo(const value: string): String;
begin
result := value;
end;
class function TMySingleton.Obj: TMySingleton;
begin
if MyObj = nil then
MyObj := Self.Create;
result := MyObj;
end;
{ TOverloadContructor }
procedure TOverloadConstructor.Create(s: string);
begin
// null method
end;
initialization
TMySingleton.MyObj := nil;
finalization
if Assigned(TMySingleton.MyObj) then
FreeAndNil(TMySingleton.MyObj);
end.
If the user
var
Singleton: TMySingleton;
begin
Singleton := TMySingleton.Create;
Design-time error occurs.
[dcc32 Error] Unit1.pas(33): E2625 Private member 'THideConstructor.Create' is inaccessible here MySingleton.pas(11): Related method: constructor Create;
enter image description here
Also, you can't see any autocomplete hints named Create.
Related
I read this answer about constructors and its directives (reintroduce, overload, virtual, override, etc.) but I can't reach the goal I want. Check the following pseudo-code (I mean the code without any directive yet):
TBaseClass = class
constructor Create;
end;
TStringStuff = class (TBaseClass)
constructor Create(str: string);
end;
TNumberStuff = class (TBaseClass)
constructor Create(num: integer);
constructor Create(num: decimal);
end;
I want a TStringStuff object can be created using its own constructor and the parent one:
var
StrStuff: TStringStuff;
begin
StrStuff:=TStringStuff.Create();
//or
StrStuff:=TStringStuff.Create('bla');
end;
but I also want a TNumberStuff object can be created using ONLY its own constructors, i.e. if someone use my library he wont be able to create a TNumberStuff without parameter:
var
NumStuff: TNumberStuff ;
begin
NumStuff:=TNumberStuff.Create(10);
//or
NumStuff:=TNumberStuff.Create(10.5);
// but NOT: NumStuff:=TNumberStuff.Create();
end;
So how to use the directives to achieve my goals?
(I am using Delphi 10.2 Tokyo)
For a variety of reasons you can't achieve exactly what you want, but this is as close as you can get.
I have introduced a 'superbase' class if you like with a hidden constructor, only visible in that unit. The only function of TBaseClass now is to 'expose' the constructor so that you can create instances of TBaseClass.
unit Test1;
interface
type
TBaseBaseClass = class
// This does all the work of TBaseClass, but hides the contructor
private
constructor Create; reintroduce; // this can only be accessed within this unit
end;
TBaseClass = class(TBaseBaseClass)
// a creatable class. No actual work is done here. It's only purpose is to
// 'expose' the base constructor
public
constructor Create; reintroduce;
end;
TStringStuff = class( TBaseBaseClass )
public
constructor Create; reintroduce; overload;
constructor Create( str : string ); reintroduce; overload;
end;
TNumStuff = class( TBaseBaseClass )
public
constructor Create( num : integer ); reintroduce; overload;
constructor Create( num : single ); reintroduce; overload;
end;
implementation
{ TStringStuff }
constructor TStringStuff.Create(str: string);
begin
inherited Create;
// ...
// other stuff
end;
constructor TStringStuff.Create;
begin
inherited Create;
// does no extra work! 'exposes' TBaseBaseClass constructor
// but required because of rules of polymorphism
end;
{ TBaseBaseClass }
constructor TBaseBaseClass.Create;
begin
inherited Create;
// ...
// other stuff - does the work originally in TBaseClass
end;
{ TBaseClass }
constructor TBaseClass.Create;
begin
inherited Create;
// does no extra work! 'exposes' TBaseBaseClass constructor
end;
{ TNumStuff }
constructor TNumStuff.Create(num: single);
begin
inherited Create;
// ...
// other stuff
end;
constructor TNumStuff.Create(num: integer);
begin
inherited Create;
// ...
// other stuff
end;
end.
In another unit, if you put a test procedure like this
procedure Test;
var
iBaseClass : TBaseClass;
iStringStuff : TStringStuff;
iNumStuff : TNumStuff;
begin
iBaseClass := TBaseClass.Create;
iStringStuff := TStringStuff.Create;
iNumStuff := TNumStuff.Create;
end;
you will find it does not compile.
But there are a couple of 'gotchas'. If you try putting this procedure in the same unit as the original definitions it will compile. That is because the TBaseBase constructor is visible within the unit.
The second gotcha is related to the fact that the hidden constructor is parameterless, and there is a public parameterless constructor for TObject, from which all objects are descended. So if you try to create an instance of TBaseBaseClass using a constructor without parameters it will compile. It just won't use the constructor you might expect. It will use the TObject constructor.
Finally I would advise against ever trying to hamstring other programmers. By all means lead them in the right direction, but don't try and stop them doing what they want to do. With that in mind, I would not do it this way. I would make the TBaseBase constructor protected, not private. I am just showing this to answer your question.
Here is how I wold deal with your problem
First I would set BaseClass constructor to be virtual and thus allow overriding it in desendant classes
TBaseClass = class
constructor Create; virtual;
end;
Then in TStringStuff class I would change your existing constructor so that its string parameter is actually an optional parameter. This would allow you to call this constructor with or without string parameter passed to it. So now ony thing you need to do is call parent constructor with the help of inherited when no parameter was passed to consturctor or do necessary work before if string paramter was passed to the constructor.
TStringStuff = class (TBaseClass)
constructor Create(str: string = ''); override;
end;
And in TNumberStuff class you just reintroduce your overloaded constructors to be able to manage multiple posible input parameter types that can be passed to the constructor.
TNumberStuff = class (TBaseClass)
constructor Create(num: integer); reintroduce; overload;
constructor Create(num: decimal); reintorduce; overload;
end;
This question already has answers here:
Delphi: Method 'Create' hides virtual method of base - but it's right there
(2 answers)
Closed 7 years ago.
There is a virtual method in the baseclass that is overridden in the subclass.
However, I need to add a new parameter in the subclass method and it's impossible to use the "override" declaration, since the parameters are different.
Example:
type
TFruit = class(TObject)
public
constructor Create; virtual;
end;
TApple = class(TFruit)
public
constructor Create(Color: TColor); override; // Error: Declaration of 'Create' differs from previous declaration
end;
I know a good practice in this situation would be creating a new method with another name, but a lot of code would be redundant. This new parameter will affect just a few lines...
Then I thought of using "overload", but then I cannot use "override" together. So I ask: is there any problem in only using "overload"? (Delphi shows a Warning: Method 'Create' hides virtual method of base type 'TFruit')
I also checked the use of reintroduce + overload (to hide the warning above), but I also saw bad recommendations about this practice. What do you think?
And last, what if I just don't use none of them, just removing "override" in the subclass method and add the new param? (which gives me the same warning)
Anyone have any suggestion about what I should do in this case, to keep the good practices?
type
TFruit = class(TObject)
public
constructor Create; virtual;
end;
TApple = class(TFruit)
public
constructor Create; override;
// What should I do:
// constructor Create(Color: TColor); overload; // Shows a warning
// constructor Create(Color: TColor); reintroduce; overload; // Hides the warning
// constructor Create(Color: TColor); // Shows a warning
// Other solution?
end;
Thanks!
Constructors in Delphi do not have to be named Create(). They can be named whatever you want. So if you need to introduce a new parameter, and it only affects a few lines of code, I would suggest creating a whole new constructor:
type
TFruit = class(TObject)
public
constructor Create; virtual;
end;
TApple = class(TFruit)
public
constructor CreateWithColor(Color: TColor);
end;
constructor TApple.CreateWithColor(Color: TColor);
begin
inherited Create;
// use Color as needed...
end;
The majority of your code would still be able to call TApple.Create(), and the few affected lines could call TApple.CreateWithColor() instead.
Otherwise, use reintroduce if you have to maintain the Create() name, and give it a default parameter so existing code will still compile:
type
TFruit = class(TObject)
public
constructor Create; virtual;
end;
TApple = class(TFruit)
public
constructor Create(Color: TColor = clNone); reintroduce;
end;
constructor TApple.Create(Color: TColor);
begin
inherited Create;
// use Color as needed...
end;
Just know that either way, if you are creating derived object instances using a class of TFruit metaclass (which is the usual reason for a virtual constructor), you won't be able to call either custom TApple constructor:
type
TFruit = class(TObject)
public
constructor Create; virtual;
end;
TFruitClass = class of TFruit;
TApple = class(TFruit)
public
constructor Create(Color: TColor = clNone); reintroduce;
// constructor CreateWithColor(Color: TColor);
end;
var
Fruit: TFruit;
Cls: TFruitClass;
begin
Cls := TApple;
Fruit := Cls.Create; // calls TFruit.Create() since it is not overridden in TApple...
//...
end;
I create my own class and I want to use it in my new component but I am getting an error...
The code is the following:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
public
procedure SetMyClass(aName: string; aNumber: double);
published
property MyClass: TMyClass write SetMyClass;
end;
procedure SetMyClass(aName: string; aNumber: double);
begin
FMyClass.Name:= aName;
FMyClass.Number:= aNumber;
end;
it appears that the property has incompatible types, I don't know why.
Does anybody has a clue about that and how can I solve this problem.
Having a FName and FNumber as fields in TMyComponent is not an option, my code is more complex and this is a simple example to explain my goal.
thanks
The things that I can see wrong with your code at present are:
The property setter must receive a single parameter of the same type as the property, namely TMyClass.
The property setter must be a member of the class, but you've implemented it as a standalone procedure.
A published property needs to have a getter.
So the code would become:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
procedure SetMyClass(Value: TMyClass);
published
property MyClass: TMyClass read FMyClass write SetMyClass;
end;
procedure TMyComponent.SetMyClass(Value: TMyClass);
begin
FMyClass.Name:= Value.Name;
FMyClass.Number:= Value.Number;
end;
This code does not instantiate FMyClass. I'm guessing that the code that does instantiate FMyClass is part of the larger component code that has been excised for the sake of this question. But obviously you do need to instantiate FMyClass.
An alternative to instantiating FMyClass is to turn TMyClass into a record. Whether or not that would suit your needs I cannot tell.
It looks like you are having some problems instantiating this object. Do it like this:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
procedure SetMyClass(Value: TMyClass);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property MyClass: TMyClass read FMyClass write SetMyClass;
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited;
FMyClass:= TMyClass.Create;
end;
destructor TMyComponent.Destroy;
begin
FMyClass.Free;
inherited;
end;
procedure TMyComponent.SetMyClass(Value: TMyClass);
begin
FMyClass.Name:= Value.Name;
FMyClass.Number:= Value.Number;
end;
One final comment. Using MyClass for an object is a bad name. Use class for the type, and object for the instance. So, your property should be MyObject and the member field should be FMyObject etc.
Try this:
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponent = class(TCustomPanel)
private
FMyClass: TMyClass;
public
procedure SetMyClass(Value: TMyClass);
published
property MyClass: TMyClass write SetMyClass;
end;
procedure TMyComponent.SetMyClass(Value);
begin
FMyClass := Value;
end;
unit MyComponentTest2;
interface
uses SysUtils, Classes, Controls, Forms, ExtCtrls, Messages, Dialogs;
type
TMyClass = class
Name: string;
Number: double;
end;
TMyComponentTest2 = class(TCustomPanel)
private
FMyClass: TMyClass;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetMyClass(Value: TMyClass);
published
property MyClass: TMyClass read FMyClass write SetMyClass;
end;
procedure Register;
implementation
constructor TMyComponentTest2.Create(AOwner: TComponent);
begin
Inherited Create(AOwner);
FMyClass:= TMyClass.Create;
end;
destructor TMyComponentTest2.Destroy;
begin
Inherited;
FMyClass.Free;
end;
procedure TMyComponentTest2.SetMyClass(Value: TMyClass);
begin
FMyClass.Name:= Value.Name;
FMyClass.Number:= Value.Number;
end;
procedure Register;
begin
RegisterComponents('MyComponents', [TMyComponentTest2]);
end;
end.
I have a parent class with one important abstract procedure which I am overloading in many child classes as the example code shown below:
TCParent = Class
private
public
procedure SaveConfig; virtual; abstract;
end;
TCChild = Class(TCParent)
private
public
procedure SaveConfig; override;
end;
Now there I need to (overload) this procedure with another SaveConfig procedure that will accept parameters, yet I don't want to make big changes in the parent class that might require that I go and make changes in all other child classes.
Is there a way I can overload SaveConfig in this specific child class without making big changes to the parent class and other child classes that inherit from it?
You can use reintroduce to add a new overloaded method. Note that the order of reintroduce; overload; in the child class is required; if you reverse them, the code won't compile.
TCParent = Class
private
public
procedure SaveConfig; virtual; abstract;
end;
TCChild = Class(TCParent)
private
public
procedure SaveConfig; overload; override;
procedure SaveConfig(const FileName: string); reintroduce; overload;
end;
(Tested in Delphi 7, so should work in it and all later versions.)
Since you do not want to make changes to other descendants, I would suggest adding an optional field to the parent class to hold the parameters, then any descendant that wants to use parameters can use them. That way, you don't have to change the signature of the overridden SaveConfig(). For example:
type
TCParent = class
protected
SaveConfigParams: TStrings; // or whatever...
public
procedure SaveConfig; overload; virtual; abstract;
procedure SaveConfig(Params: TStrings); overload;
end;
procedure TCParent.SaveConfig(Params: TStrings);
begin
SaveConfigParams := Params;
try
SaveConfig;
finally
SaveConfigParams := nil;
end;
end;
.
type
TCChild = class(TCParent)
public
procedure SaveConfig; override;
end;
procedure TCChild.SaveConfig;
begin
if SaveConfigParams <> nil then
begin
// do something that uses the parameters...
end else begin
// do something else...
end;
end;
.
type
TCChild2 = class(TCParent)
public
procedure SaveConfig; override;
end;
procedure TCChild2.SaveConfig;
begin
// do something, ignoring the SaveConfigParams...
end;
Yet another in my series of questions regarding constructors in Delphi.
i have a base class that has has the virtual constructor:
TComputer = class(TObject)
public
constructor Create(Teapot: Integer); virtual;
end;
The constructor is virtual for the times that someone needs to call
var
computerClass: class of TComputer;
computer: TComputer;
begin
computer := computerClass.Create(nTeapot);
The constructor is overridden in descendants:
TCellPhone = class(TComputer)
public
constructor Create(Teapot: Integer); override;
end;
TiPhone = class(TCellPhone )
public
constructor Create(Teapot: Integer); override;
end;
Where TCellPhone and TiPhone descendants each have their opportunity to do their own initialization (of members not included for readability).
But now i add an overloaded constructor to some ancestor:
TCellPhone = class(TComputer)
public
constructor Create(Teapot: Integer); override; overload;
constructor Create(Teapot: Integer; Handle: string); overload;
end;
The alternate constructor in TCellPhone calls the other virtual constructor, so it always gets the proper overridden behaviour:
constructor TCellPhone.Create(Teapot: Integer; Handle: string);
begin
TCellPhone.Create(Teapot); //call sibling virtual constructor
FHandle := Handle;
end;
The problem is that the descendant, overridden, constructor is never called. The actual stack trace chain of calls is:
phone := TiPhone.Create(37, 'spout')
constructor TCellPhone.Create(Teapot: Integer; Handle: string)
constructor TCellPhone.Create(Teapot: Integer)
constructor TComputer.Create(Teapot: Integer)
TObject.Create
The sibling call to TCellPhone.Create(int), which is virtual, should have called the descendant, overridden, method in TiPhone:
phone := TiPhone.Create(37, 'spout')
constructor TCellPhone.Create(Teapot: Integer; Handle: string)
constructor TiPhone.Create(Teapot: Integer)
constructor TCellPhone.Create(Teapot: Integer)
constructor TComputer.Create(Teapot: Integer)
TObject.Create
So it seems that attempts to use a sibling virtual constructor is Delphi do not work as expected.
Is it then a bad idea for one constructor to use another? Is the design intention that code in overloaded constructors be copy-paste versions of each other?
i notice in .NET that some constructors chain to each other:
public Bitmap(int width, int height) : this(width, height, PixelFormat.Format32bppArgb) {}
public Bitmap(int width, int height, PixelFormat format) {...}
This only seems to be a problem if:
a constructor is virtual
you overload the constructors
Is the rule that you cannot have one constructor overload another?
Errr..
constructor TCellPhone.Create(Teapot: Integer; Handle: string);
begin
TCellPhone.Create(Teapot); //call sibling virtual constructor
FHandle := Handle;
end;
That should be:
constructor TCellPhone.Create(Teapot: Integer; Handle: string);
begin
Create(Teapot); //call sibling virtual constructor
FHandle := Handle;
end;
You were just creating a new TCellphone instance and not calling the other Create method.