LabVIEW Idea Exchange

cancel
Showing results for 
Search instead for 
Did you mean: 
Daklu

Class constructors

Status: Declined

Any idea that has received less than 8 kudos within 8 years after posting will be automatically declined.

I'm finding that often my classes have some sort of dynamic private data that needs to be initialized before the object will work correctly.  (Queues, User Events, etc.)  Currently I have to implement some sort of initialization vi that the class user must call every time an object is created.  If the user forgets to do this Labview raises an invalid refnum error.  There are workarounds such as wrapping the class in a .lvlib, using class factories, or checking the queue refnum with every class sub vi.  However, they are workarounds that require extra coding and add complexity.

 

I'd like to have the ability to define a private class constructor that fires behind the scenes every time an object constant or control returns the default value during execution.  With this ability I can be certain the object's dynamic resources have been allocated correctly and it simplifies the api for class users.

18 Comments
AristosQueue (NI)
NI Employee (retired)
Please read the LVOOP Decisions Behind The Design document for why this is an impossible request to fulfill. Search for the section on "constructors".
Daklu
Active Participant

I have read that document... several times in fact.  I even have a well-worn copy in my file cabinet.  The document does an excellect job of explaining many of the decisions you've made in implementing LVOOP.  But I don't see anything in the document that describes why the idea I described can't or shouldn't be implemented. 


The Decisions Behind the Design:

Let's consider the use cases for constructors:

  1. Setting the initial values of an object.
  2. Calculating the initial values of the object from a set of parameters.
  3. Calculating the initial values of the object from environment data (such as recording the timestamp when the object was constructed).
  4. Reserving system resources (memory, communication channels, hardware, etc) for use by this object (to be freed later by the destructor).

LabVIEW has the ability to set the default value for your class. The values that you set in your private data control are the default values for your class. Now, are these calculated values? No. They are static. You do not have any place to put running code as part of your default value. This is the same as a simple default constructor in other OO languages. The initial value for all instances of the class is just this default value, so Constructor Use Case #1 is fulfilled.


 

 

I think there is some confusion due to our different perspectives in talking about the this.  The term "constructor" to you means the c++ method that fires when an object is placed on the block diagram.  From a Labview user's point of view an object is "constructed" when the execution engine puts the default data on the class constant's output wire.  Until the data is visible on a block diagram wire it is unusable and might as well not exist.  Anything that happens before the data is sent out of the class constant is essentially a constructor from our point of view.

 

Obviously I didn't express my idea very well the first time around.  What I'm proposing is a change in run time object behavior, not design time object behavior.  At design time everything stays the same; I drop an object and the refnums are all initialized to zero.  Why can't we have a constructor block diagram that automatically executes when the run time engine encounters a class contant or class control that is returning the default data.  (i.e. An unwired class input on the connector pane.)  Maybe the constructor vi can't accept any inputs and only has a single output for class data.  (Although an error node might be a good idea too.)  Maybe the class constant block shouldn't be considered a constant anymore.  If it were a "creator" block instead it should be possible to address case #2.  In fact, defining the constructor as an execution environment operation should make cases 1-3 viable.

 

It is possible to implement this behavior in Labview by wrapping a lvlib around a private class, creating a "Create New" vi, and wrapping all the class methods in library vis.  (Of course doing this means I can't inherit from that class.)  My question is why do I have to keep doing it?  I'd like to see that capability pushed down to the native api.

Intaris
Proven Zealot
So you mean something like the "Execution state change" event for XControls?
Daklu
Active Participant

That would work for case 1.  Cases 2 and 3 would only work if the constructor event fired when the execution engine arrived at that point on the block diagram since those cases open the possibility of wiring into the class block.

JackDunaway
Trusted Enthusiast

Necroposting:

 

Daklu, basically you're asking for a formalized Factory method whose sole purpose is to produce one instance of the containing class based on inputs to that Constructor (Factory method)?

 

This seems like a great idea - can someone explain to me why it's a bad idea? Also, Daklu, do you still support this Idea, or have you changed your mind in the past year?

 

Thanks!

Daklu
Active Participant

Jack,

 

I hadn't thought about it quite like that but I suppose it's one way of looking at it.  However, in my idea the factory method would be hidden from the class user and execute behind the scenes, meaning the class user can't send inputs to it.  Do I still support the idea?  I support resolving the problem this idea addresses, but I don't know if this idea is the best way to it.  (In fact there are some significant issues with this idea.)

 

The main problem with using run-time references (queues, DVRs, etc.) in a class is that implementation details are leaked to the class user.  Not details about what kind of reference is used, but the detail that a reference is used at all.  Classes that have private references require a different code pattern from the class user--they require some sort of Init method.  This exposed detail makes it difficult to implement certain kinds of changes that should be transparent to the user.

 

Suppose I create a class that has a private circular buffer implemented using an array.  It can be implemented as a regular by-value class; no Init required.  If I later decide to change the circular buffer from an array to a queue I have to add an Init method which must be called prior to any other class methods.  Now the class user's code is going to throw run-time errors until Init methods are added after every class constant on the block diagram.  I believe I, as a class designer, should be able to make that change transparent to the user.  In more general terms, I believe I should be able to make classes that use run-time references internally behave identically to by-val classes.

 

What are some unresolved issues with this idea?

 

1.  Currently you can drop n class constants on the block diagram with minimal incremental overhead.  At runtime LV creates a single class data instance and n pointers referring to that instance.  When the data on one of the class wires changes LV creates a new class data instance and changes the appropriate data.  If a hidden constructor were implemented LV would have to create n instances, increasing memory use.  [Counter argument - LV is going to end up creating n instances anyway with an Init method following each class constant.]

 

2.  In those cases where an object constant is used strictly for it's type information, such as the 'To More Specific Class' prim, run-time references are needlessly allocated.  Resources are wasted.

 

3.  Hidden constructors implies hidden destructors, but destructors are meaningless in data flow languages.  (See AQ's article linked above for details.)  How to resolve this?

 

Perhaps the problem is with my assertion, "I should be able to make classes that use run-time references internally behave identically to by-val classes."  Any class that uses an (initialized) run-time reference is going to behave in a fundamentally different way than a by-value class when the wire branches.  Duplicating by-value behavior would require the constructors be executed at wire branches and requires a new copy of the referenced data.  This could have a major impact on the compiler's (and/or run time engine's) internal reference counting algorithms.

 

Maybe it's better that reference classes require an Init method so the class user is aware of what will happen when the wire branches?  Maybe rather than hidden class constructors a better solution is (for example) a self-initializing by-value queue?  Is it even possible for NI to create by-value references that don't require explicit run-time initialization?  I don't really know the answers to these questions.

 

There is one thing AQ mentioned that I haven't fully explored.

 

"Constructor Use Case #3 is a more advanced concept for software programmers. LabVIEW has support for this concept, but not in an obvious way. Counting how many instances of the class exist (using a class static field), or recording the timestamp of a given instantiation is functionality most classes do not need. We left such functionality for a more advanced tool. You construct an XControl of your class' type. The XControl has code that runs at edit time and at run time to initialize values of the class. In the Façade VI you can include any code you need to set the values for the instance of your data. Creators of the class may find this a bit cumbersome today. It is certainly something that we hope to make easier over time. But it is the correct place to put this functionality – in the code that underlies the control."

 

Could this be used to automatically obtain a queue refnum in a way that's transparent to the class user?  I don't know.  The little bit of xcontrol stuff I've done has been UI oriented, not code oriented.

Intaris
Proven Zealot

If we could prevent the class constants from being placed on a BD outside of its own class (or friends) then we can offer the users a VI with a pre-initialised objects as the only entry point into the code.

JackDunaway
Trusted Enthusiast

@Intaris wrote:

If we could prevent the class constants from being placed on a BD outside of its own class (or friends) then we can offer the users a VI with a pre-initialised objects as the only entry point into the code


This is the principle behind what I'm ignorantly referring to as the "factory method", or "constructor" (emphasis on the quotes)


@Daklu wrote:

However, in my idea the factory method would be hidden from the class user and execute behind the scenes, meaning the class user can't send inputs to it.


Then this is where we currently differ, because I would envision the "factory method" being a public method with the inputs determining the constructed class type and initial values.

Daklu
Active Participant

The only way I know of doing that is by wrapping the class in a library, making it private, and creating public library methods that call the class methods.  Problems with that:

 

1.  Users can't use the class constant for type purposes.

2.  Users can't derive their own classes from the private class.

3.  It's not even close to being transparent unless I do that with all my classes.

 

I don't think restricting access to the class constant is the right solution.  The ask is to make the change from a by-value class to a by-reference class transparent to the class user.  Preventing class constants from being used on the bd doesn't do that.  (Unless all class constants are removed from the block diagrams, which would be a scorched earth change.)
AristosQueue (NI)
NI Employee (retired)

 


@Intaris wrote:
If we could prevent the class constants from being placed on a BD outside of its own class (or friends) then we can offer the users a VI with a pre-initialised objects as the only entry point into the code.

 

Not just diagram constants. You would also need these restrictions (or variations on them):

 

1. Parent classes must be prevented from having default values of your class type (i.e. "Make Current Value Default" banned because data lingering from the previous execution may not be viable in the current execution -- contained refnums might not be valid anymore; records of system state might not be up-to-date; etc)

2. To have all controls and indicators of your class on the front panel be connected to the conpane and all inputs must be Required inputs.

3. On the diagram, the class input would be required to propagate to the class output, just as is required for dynamic dispatch outputs currently, even on static dispatch VIs.

4. Any VI with your class in its conpane would be banned from running as a top level VI (so that those required inputs are guaranteed to be wired from somewhere instead of just generating their default values).

5. Reading the Value property for controls of your class type would be an error.

7. Uninitialized Shift Registers for your class type would need to be banned (for same reason as #1)

7. Global VIs of your class type would need to be banned.

8. When LV needs to generate a default value of your class (ie., a Dequeue primitive that times out and needs to put a value on its output), LV would need to be taught to call your constructor VI, probably with some boolean input that allows you to construct your object differently if it arises from this "autogenerated default value" use case.

9. Unflattening of your class type would be banned OR some sort of "run this VI after unflattening data of this class" feature would need to be added to LV classes (which is a good idea anyway, but would be a prerequisite for this one).

10. These restrictions would apply to any descendant class of your class type.

 

In essence, the restrictions needed to make this work are the same restrictions needed to make a truly Abstract class whose value is never ever seen at runtime (except Abstract classes do not require restriction #9). Having a well-defined runtime constructed value, whether of yourself or of your children, requires modifying/handling/banning all the places where LV creates data ex nihilo.