Return value of __New() and alternative way of instantiating a class object

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Return value of __New() and alternative way of instantiating a class object

Post by Flipeador » 05 Oct 2019, 15:51


How do I indicate an error when trying to create an instance of a class object through its __New() constructor without using Throw?.

Code: Select all

class Num
{
    Value := 255
    __New(Number)
    {
        local ReturnValue := Number is "Number" ? this : 0
        MsgBox("ReturnValue: " . ReturnValue . "`nValue: " . this.Value)
        return ReturnValue
    }
}

n := Num.New("XXX")
MsgBox Type(n)  ; I expected Integer, 0.
ExitApp


What is the correct way to instantiate a new class object without "passing" through its constructor __New().

Code: Select all

oImg1 := Image.New(255, 255)

pImg  := CreateAnImage(16, 16)
oImg2 := Image.Instance.New(pImg)

MsgBox Type(oImg1)
MsgBox Type(oImg2)
ExitApp


class Image
{
    __New(Width, Height)
    {
        this.Handle := CreateAnImage(Width, Height)
    }

    class Instance extends Image
    {
        __New(Handle)  ; Image from handle.
        {
            this.Handle := Handle
        }
    }
}

CreateAnImage(Width, Height)
{
    return 1  ; Handle.
}

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Return value of __New() and alternative way of instantiating a class object

Post by Helgef » 06 Oct 2019, 00:07

1) see :arrow: Class.new.
2) I don't understand the question, the answer might be the same as 1) though.

Cheers.

User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Re: Return value of __New() and alternative way of instantiating a class object

Post by Flipeador » 06 Oct 2019, 08:38

I'd already read the decumentation on the link you provided; But I see no way to do it from __New(), do I have to use static New()?.
I'd appreciate an example of how you would do it.
:wave:

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Return value of __New() and alternative way of instantiating a class object

Post by Helgef » 06 Oct 2019, 12:39

do I have to use static New()?.
yes, this has several benefits over the v1 behaviour.

User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Re: Return value of __New() and alternative way of instantiating a class object

Post by Flipeador » 06 Oct 2019, 22:11

I'm not sure about this. I'd like to see an example anyway. if I change __New() to static New() in my first example, .Value is unassigned.
How is ignoring the return value of __New() a positive thing?.
Sorry, looks like I'm missing something, I appreciate if someone takes the time to explain. :crazy:

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Return value of __New() and alternative way of instantiating a class object

Post by Helgef » 07 Oct 2019, 11:05

if I change __New() to static New() in my first example, .Value is unassigned.
When you call a static method, the this parameter refers to the class itself (or a subclass). So in your case, the .Value would be assigned to the Num class.

You should implement you own new() method, verify the input and only if then, you create and initialise the new object, example,

Code: Select all

class Num
{
    Value := 255 
    __New(Number)
    {
        this.number := number
    }
	static new(number) {
		if Number is "Number"
			return base.new(number)
        return 0
	}
}
We now avoid creating the object on invalid input, we also avoid all initialisation in __init and we avoid dealing with objects which have failed initialisation in __delete. Also, the unintuitive behaviour of explicit return vs no return in __new is gone :thumbup: .

Cheers.

User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Re: Return value of __New() and alternative way of instantiating a class object

Post by Flipeador » 07 Oct 2019, 12:08

So... this example (which would work in previous versions):

Code: Select all

Img := Image.New(32, 32)
MsgBox Format("Img:`s{}`nType:`s{}`nHandle:`s{}", Img, Type(Img), IsObject(Img)?Img.Handle:"Error")

class Image
{
    Handle := 0
    __New(Width, Height)
    {
        this.Handle := CreateImage(Width, Height)
        if (this.Handle == 0)
            return 0
    }
}

CreateImage(Width, Height) {
    return 0  ; Suppose this function creates an image and fails, returning an invalid handle.
}
Should it be like this?:

Code: Select all

Img := Image.New(32, 32)
MsgBox Format("Img:`s{}`nType:`s{}`nHandle:`s{}", Img, Type(Img), IsObject(Img)?Img.Handle:"Error")

class Image
{
    Handle := 0
    __New(Handle, Width, Height)
    {
        this.Handle := Handle
    }
    static New(Width, Height)
    {
        local Handle := CreateImage(Width, Height)
        if (Handle == 0)
            return 0
        return base.new(Handle, Width, Height)
    }
}

CreateImage(Width, Height) {
    return 0  ; Suppose this function creates an image and fails, returning an invalid handle.
}

And if I have something like this, should I initialize local variables Width and Height in (2)?:

Code: Select all

Img := Image.New()
MsgBox Format("Img:`s{}`nType:`s{}`nHandle:`s{}", Img, Type(Img), IsObject(Img)?Img.Handle:"Error")

class Image
{
    ; Instance Variables.
    Handle := 0
    Width  := 32
    Height := 32
    ; Constructor.
    __New(Handle)
    {
        this.Handle := Handle
    }
    static New()
    {
        ; (2)
        local Handle := CreateImage(Image.Width, Image.Height)  ; (?) *
        if (Handle == 0)
            return 0
        return base.new(Handle)
    }
}

CreateImage(Width, Height) {
    MsgBox "Width/Height: " . Width . "/" . Height  ; (?) *
    return 1  ; Handle.
}


How would I do with the second example of my first post?:
(I want to call __New without going through static New)

Code: Select all

oImg1 := Image.New(255, 255)

Handle := CreateAnImage(16, 16)
;oImg2  := Image.__New(Handle)
oImg2  := Image.Instance.New(Handle)

MsgBox "Type(oImg1): " . Type(oImg1) . "`nType(oImg2): " . Type(oImg2)
ExitApp

class Image
{
    __New(Handle)
    {
        MsgBox("Image __New()")
        this.Handle := Handle
    }

    static New(W, H)
    {
        MsgBox("Image static New()")
        local Handle := CreateAnImage(W, H)
        return Handle ? base.new(Handle) : 0
    }

    class Instance extends Image
    {
        __New(Handle)
        {
            MsgBox("Image.Instance __New()")
            this.Handle := Handle
        }
        static New(Handle)
        {
            MsgBox("Image.Instance static New()")
            return base.new(Handle)
        }
    }
}

CreateAnImage(W, H)
{
    return 1  ; Handle.
}
Thanks.

User avatar
Flipeador
Posts: 1204
Joined: 15 Nov 2014, 21:31
Location: Argentina
Contact:

Re: Return value of __New() and alternative way of instantiating a class object

Post by Flipeador » 12 Oct 2019, 15:29

So... I've decided to use a function, something like this:

Code: Select all

MsgBox Type(Image(32,32,0))
MsgBox Type(Image(32,32,1))

class Image
{
    Ptr := 0
    __New(Ptr) => this.Ptr := Ptr
    static New(Ptr) => Ptr ? base.New(Ptr) : 0
    __Delete() => this.Ptr && MsgBox(A_ThisFunc)
}

Image(W, H, R)
{
    return Image.New(CreateImage(W,H,R))
}

CreateImage(W, H, R)
{
    return R  ; Handle.
}
If anyone is interested, this seems to do the trick with respect to my other doubt:

Code: Select all

Cls.New().Method()
ClassInst(Cls).Method()

class Cls
{
    __New() => MsgBox(A_ThisFunc)
    Method() => MsgBox(A_ThisFunc)
}

ClassInst(ClassObj)
{
    local ClassInst
    return (ClassInst:=Class.New())
        && (ClassInst.base:=ClassObj.Prototype)
        && ClassInst
}

lexikos
Posts: 9665
Joined: 30 Sep 2013, 04:07
Contact:

Re: Return value of __New() and alternative way of instantiating a class object

Post by lexikos » 12 Oct 2019, 19:23

Class.New() is for creating a new class. To create a new object of the more general kind, use Object.New() or {}.

In my opinion, the correct way to instantiate a class is to use static new() and accept the possibility of an exception in the case where the class cannot be instantiated. A caller should be able to assume that static new() always returns an object (but it doesn't have to be a newly constructed one). If you want different behaviour, you can use a different factory method or function that constructs the object manually (or calls static new() only once it is certain the new instance can be constructed).

For example:

Code: Select all

Image(W, H, R) => (p := CreateImage(W,H,R)) && Image.New(p) ; where New(p) returns an object or throws.
It is also okay to dispose of pre-established convention and choose or invent a different one, but there are disadvantages, and I would not describe it as correct.

If you want to construct an object (not instantiate a class) without having __New() called, you can do it the same way that it was done before classes were added to the language; i.e just do it.

Code: Select all

obj := {}  ; Create an object.
obj.base := Cls.Prototype  ; Do whatever you want with it.
obj2 := {base: Cls.Prototype}  ; Or do it in one step.
The catch is that the object and its base must be of the same native type (Object, Array, Map, File, etc.). If you want to manually construct a custom array based on Array, for instance, you must start with Array.New() rather than {}. In that case, __New() is only called if someone defined it on Array.Prototype, which is unlikely. Class.New() worked for you because currently instances of Class and instances of Object have the same native type.

There are two reasons for the restriction:
  • Native type is currently determined by the chain of base objects. If you change the base object incorrectly (such as via NumPut), the built-in type checks will fail; there will be some built-in method-functions that can be applied to the object but will crash the program, and others that cannot be applied even though they would work.
    - It is done this way to avoid reserving extra space for type information, or the increased code size of other methods I tested (such as packing the native type into otherwise unused bits). C++ runtime type information seems to be insufficient (while simultaneously having parts we don't need that would bloat the executable).
  • Even if you could base an object of one type on an object of another type, only the generic Object functionality (and user-defined methods) would work. For instance, none of the Array functionality works on Array.Prototype itself, because it is natively just an Object.
There are a couple of small issues:
  • To manually construct a "Prototype" for a sub-class of a specialized type, you must start with an instance of that type. For instance, {base: Array.Prototype} won't work, but you can use an actual Array ([] or Array.new()) as a prototype. By contrast, the class syntax constructs an Object but flags it as a prototype, so calling specialized methods directly will throw a type mismatch instead of crashing the program.
  • The primitive value prototypes don't have any Object methods, so you have to apply them manually to define new members. (You may consider this the cost of messing with global predefined objects. ;))

Post Reply

Return to “Ask for Help (v2)”