5

This doesn't work:


clsTestDefaultInstance

Dim HowAmIInitialised As Integer

Private Sub Class_Initialize()
HowAmIInitialised = 99
End Sub

Public Sub CallMe()
  Debug.Print "HowAmIInitialised=" & HowAmIInitialised
End Sub

i.e clsTestDefaultInstance.CallMe() outputs HowAmIInitialised=99 because Class_Initialize() is called even for the default instance.

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "clsTestDefaultInstance"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Compare Database
Option Explicit

' test how class instance can tell if it is default
'clsTestDefaultInstance

Dim HowAmIInitialised As Integer

Private Sub Class_Initialize()
  HowAmIInitialised = HowAmIInitialised + 1
End Sub

Public Sub CallMe()
  Debug.Print "HowAmIInitialised=" & HowAmIInitialised
End Sub
  • There is no default instance for user-defined classes. (There is for UserForms, but even then you don't have to use it.) Given that `clsTestDefaultInstance` is the class name, `clsTestDefaultInstance.CallMe()` is a runtime error without `Option Explicit` and a compile time error with it. – GSerg Oct 20 '18 at 19:41
  • 2
    @GSerg: I am afraid but that is not correct. You set `VB_PredeclaredId =True` and then you can create a global default instance of your user-defined class. Question is: Why need the OP to find out if he is using a default instance of his own class. That seems strange to me. Either you know it when you write the code or ... – Storax Oct 20 '18 at 19:47
  • @Storax I did not know that attribute existed (I personally only used `NewEnum`). Then the question is a bit confusing, because the manual use of an attribute is something you want to mention, and because it actually does work exactly as it should, so it's unclear what the OP expected instead. – GSerg Oct 20 '18 at 19:54
  • @GSerg: To set `VB_PredeclaredId =True` is a little bit cumbersome because you have to export the class and edit the exported file and re-import it then. Have a look [here](https://christopherjmcclellan.wordpress.com/2015/04/21/vb-attributes-what-are-they-and-why-should-we-use-them/). But as I said it seems strange to me wanting to know if one uses the default instance of your own user defined class. I think the OP has to clarify. – Storax Oct 20 '18 at 19:58
  • @GSerg can be used for [Factories: Parameterized Object Initialization](https://rubberduckvba.wordpress.com/2018/04/24/factories-parameterized-object-initialization/) – ComputerVersteher Oct 20 '18 at 20:21
  • Clarification 1/ I ought have mentioned the setting of `VB_PredeclaredId =True` 2/ Example use: so a sub or function with intended "static" usage can enforce it is being executed from default instance only. – Martyn Barry Oct 21 '18 at 05:03
  • 1
    Damn newbies, can't they ask a question properly! Full class listing in edited Question... – Martyn Barry Oct 21 '18 at 05:08
  • 1
    So, do I get you rigt you would like to know within the class if it was called by the global default instance or if it was called by a variable you declared, right. I do not know if it's possible, sorry. But anyway, why would you need to know that? You write the code and you know if it's the default instance or not. Maybe you are looking for something like a [constructor](https://stackoverflow.com/a/46414650/6600940) or [factory](https://rubberduckvba.wordpress.com/2016/01/11/oop-in-vba-immutability-the-factory-pattern/). – Storax Oct 21 '18 at 06:07
  • @Storax Thanks, I had seen those; Interfaces are a good and proper solution. I can't answer your question coherently but the question stands :-) – Martyn Barry Oct 21 '18 at 06:26
  • @Martyn Barry: I am sorry but I do not know how to find out if an instance of a object is the global default instance or not. – Storax Oct 21 '18 at 06:31
  • I'm still not sure what is the general direction of your question. If you're wondering why `Class_Inintialize` is called for default instances, it's because they don't know they are default instances, and a class needs to execute its constructor anyway. If you're wondering why `HowAmIInitialised` does not keep its value between instances, it's because there is no class-level static variables in VBA, and you would need to emulate that behaviour (in the simplest case, by moving `HowAmIInitialised` to a non-class module, but then it will be globally visible for other code to mess with). – GSerg Oct 21 '18 at 07:13
  • 1
    @GSerg The question is valid, even if it presently has no justification: I'm looking for some intrinsic dynamic distinction between the global default and explicitly created instances. It occurred to me that `Class_Initialize()` might not be called for the global default, but my test shows it is (as does putting a breakpoint on that routine) – Martyn Barry Oct 21 '18 at 09:00
  • 1
    Is this a reasonable motivation: to use the global instance as a singleton. Program to fail if it discovers any explicitly created instance. – Martyn Barry Oct 21 '18 at 20:59

2 Answers2

8

This is really, really simple... just compare the object pointer of the instance to the object pointer of the default instance:

'TestClass.cls (VB_PredeclaredId = True)
Option Explicit

Public Property Get IsDefaultInstance() As Boolean
    IsDefaultInstance = ObjPtr(TestClass) = ObjPtr(Me)
End Property

Testing code shows that it works just fine:

Private Sub TestDefaultInstance()
    Dim foo(9) As TestClass

    Dim idx As Long
    For idx = LBound(foo) To UBound(foo)
        If idx = 5 Then
            Set foo(idx) = TestClass
        Else
            Set foo(idx) = New TestClass
        End If
    Next

    For idx = LBound(foo) To UBound(foo)
        Debug.Print idx & foo(idx).IsDefaultInstance
    Next
End Sub

With that said, note that this comes with a couple caveats:

  • It pretty much guarantees that the default instance will be reinstantiated if you check to see if any instance is the default instance, because as you probably know, simply referencing the default instance will new it back up if it isn't already instantiated.
  • The default instance can change if you Unload it (for UserForm's) or set it to Nothing and then cause it to auto-instantiate again. It's best to think of VB_PredeclaredId as kind of like a contract that you will always get an instance back if you use the class name directly. That contract does not guarantee that it will always be the same one. Adding the following code to the bottom of the TestDefaultInstance procedure above will demonstrate:

    'This doesn't effect anything that stored a reference to it.
    Set TestClass = Nothing
    'Make a call on the default to force it to reinstantiate.
    Debug.Print TestClass.IsDefaultInstance
    'This will now be false.
    Debug.Print foo(5).IsDefaultInstance
    
Comintern
  • 21,855
  • 5
  • 33
  • 80
  • 1
    Thanks, upvoted. Of course, you just have to compare the "addresses". Sometimes you don't see the wood for the trees :-) – Storax Oct 21 '18 at 18:54
  • @Storax You just have to look for the [leaks in the abstractions](https://en.wikipedia.org/wiki/Leaky_abstraction). ;-) – Comintern Oct 21 '18 at 18:59
  • @Storax and Comintern: speaking of abstraction level, a more "high level" code may be `IsDefaultInstance = Me Is TestClass`; of course, inside the class module – Marcelo Scofano Diniz Apr 08 '20 at 23:38
0

You can obtain the default instance by using the Class_Initialize and a Static Function within the class to store the default instance.

Using an example extract of my class clsCustomer which has it's VB_PredeclaredId = True

'Note Class_Initialize is called the first time the clsCustomer is accessed
'You can also do things like If Not Me Is clsCustomer for singleton classes i.e cannot create an instance other then default instance

Private Sub Class_Initialize()

    If Me Is clsCustomer Then
        GetDefaultInstance
    End If
End Sub


Static Function GetDefaultInstance() As clsCustomer

    Dim pvtDefaultInstance As clsCustomer
    If pvtDefaultInstance Is Nothing Then
        If Not Me Is Nothing Then
           Set pvtDefaultInstance = Me
       End If
    End If
    Set GetDefaultInstance = pvtDefaultInstance
End Function

In a module to test

Sub TestDefaultInstance()

    Dim pvtCustomer As clsCustomer
    Debug.Print ObjPtr(clsCustomer.GetDefaultInstance)
    Debug.Print ObjPtr(pvtCustomer)
    Set pvtCustomer = New clsCustomer
    Debug.Print ObjPtr(clsCustomer.GetDefaultInstance)
    Debug.Print ObjPtr(pvtCustomer)
    Debug.Print IsDefaultInstance(clsCustomer.GetDefaultInstance, pvtCustomer)
End Sub

Public Function IsDefaultInstance(byval defaultObject as Object, byval compareObject as Object) As Boolean

    Dim isDefault as Boolean
    if defaultObject is compareObject then
      isDefault = True
    End if
    IsDefaultInstance = isDefault 
End Function

Output:

 2401988144720    (The default instance)

 0                (The pvtCustomer instance not yet set and equal to nothing)

 2401988144720    (The default instance)

 2401988142160    (The new pvtCustomer instance which isn't the same as the default instance)

False   (False returned as the customer default object instance isn't the same as the new pvtCustomer object)

Notes: Output ObjPtr's will vary each run i.e they are memory references and only for example.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
M. Johnstone
  • 72
  • 1
  • 6