Jump to content

Recast an object


Recommended Posts

Is it possible to dynamically recast a parent object to a child object? Here's what I am trying to do: I am testing two types of parts, call them Part A and Part B. I have a "Part A" class and "Part B" class, each of which inherit from a "Part" class. I create a "Part" object, and call its method to get the part's serial number (the procedure is the same for all part types). Once I know its serial number, I can determine which type of part it is. So at this point, I want to cast my "Part" object to a (say) "Part A" object.

From all the examples I have seen, you have to create your object with the create method from whatever class you are trying to create, and it forever stays that class (even if sometimes it is represented by the wire type of a parent class--in this case you can use the "To More Specific Class" primitive to get the wire of the class that was created).

I think this recasting could be theoretically done using LVOOP with a VI that creates a new child object, copies the data from the input parent object to the new child object, and sends the child object out via a parent object terminal; but the compiler doesn't allow this. I'm not really sure how I would go about this using GOOP (I'm using GOOP Developer Suite 3). I see that there is a function for "cast to more specific" in the GOOP Kernel VI, but I'm not sure how to use it (if it should be used).

The fact that this is forbidden by the LV compiler (for LVOOP) leads me to believe that there is a good reason to not do what I am trying to do. Does anyone have any suggestions on how to do it, or some better implementation? Thanks.

Link to post

QUOTE (Jeff @ Dec 19 2008, 04:39 PM)

Is it possible to dynamically recast a parent object to a child object? Here's what I am trying to do: I am testing two types of parts, call them Part A and Part B. I have a "Part A" class and "Part B" class, each of which inherit from a "Part" class. I create a "Part" object, and call its method to get the part's serial number (the procedure is the same for all part types). Once I know its serial number, I can determine which type of part it is. So at this point, I want to cast my "Part" object to a (say) "Part A" object.

From all the examples I have seen, you have to create your object with the create method from whatever class you are trying to create, and it forever stays that class (even if sometimes it is represented by the wire type of a parent class--in this case you can use the "To More Specific Class" primitive to get the wire of the class that was created).

I think this recasting could be theoretically done using LVOOP with a VI that creates a new child object, copies the data from the input parent object to the new child object, and sends the child object out via a parent object terminal; but the compiler doesn't allow this. I'm not really sure how I would go about this using GOOP (I'm using GOOP Developer Suite 3). I see that there is a function for "cast to more specific" in the GOOP Kernel VI, but I'm not sure how to use it (if it should be used).

The fact that this is forbidden by the LV compiler (for LVOOP) leads me to believe that there is a good reason to not do what I am trying to do. Does anyone have any suggestions on how to do it, or some better implementation? Thanks.

The reason you can't cast a parent object to a child is because there's no way for the parent object to be created with all of the methods/properties any of its children may have. So, if you were to cast the parent object to the child and then call some method specific to the child (or try to access some property) it simply would not exist. That's not true of casting a child to a parent where you can always be sure the properties/methods of the parent do exist in the child. So in this case, it sounds like an approach would be to find the type of the parent and then just instantiate a child object of the correct type with the given S/N and then discard the parent (it did its job of carrying the S/N info).

Mark

Link to post

I could be wrong, but I don't think there's a way to directly recast a parent object into a child object. I would have tried the same thing you did--create a child object with a copy of the parent data. AFAIK that should work as long as you have the correct methods exposed. What error did the compiler give you when you tried this?

This link [http://decibel.ni.com/content/docs/DOC-2875] doesn't directly answer your question, but it sounds like you are implementing a Factory Pattern.

Link to post

What you want is a new object based on an existing object. You happen to be asking about the special case of having a parent object and wanting to create a child object. You think, "Well, gee, I already have the parent fields, surely the programming language will just let me fill in the values, and then I can set the child values. Why do I have to create some function that copies all the parent values into my new object?!" But that special case of parent-to-child has all the same problems of arbitrary object-to-object. Let's examine...

Suppose you have class Thing and class Stuff. Both of them have exactly the same fields for private data --- an integer, a string and a path. Now, because they have the same private data, LabVIEW could, in theory, allow you to cast a Thing into a Stuff or vice versa. But that isn't allowed. Why? There are two reasons:

  1. Those fields have meaning for their objects. In the Thing class, perhaps that integer is always an odd number and in the Stuff class the integer is always an even number. Perhaps Thing enforces a rule that either the String or the Path are always empty, but never both, where as the Stuff class never allows either one to be empty. The APIs of each class control the data values that can be set in objects of that class type.
  2. The data is private. Assigning from one object to another directly because the fields happen to be the same would mean that we were taking public advantage of private knowledge, and that if at some point the private details of either class were changed then public functionality would break. That's exactly the sort of thing that is not supposed to happen.

So, obviously, simply changing one to another cannot be a built-in part of the language. Any time you want to be able to create a new object from an existing object of another type, you, the LV user, have to write the transformation function and assign the fields as desired.

Now, you're interested in the more specific case of parent-to-child. In this case, we can rule out reason #2 because any time the parent class changes, the child class will change in the same way, so the cast would always be possible from a pure "can this data be copied here" standpoint. But reason #1 still applies:

  • Suppose Parent defines an integer and a dynamic dispatch method "Set Integer.vi". The Parent implementation takes an integer as input, multiplies it times 2 and bundles it into the cluster.
  • Child has its own override of "Set Integer.vi" which takes the input, multiplies it times two, and then does Call Parent to the parent verson.

In this case, the Parent API has enforced a rule to make sure that the integer is always an even number. The Child class has gone further and made sure that this integer is divisible by four. The fields that a child class inherits from its parent are settable only through the parent's API and thus must conform to the parent's rules. But the child class can put more restrictive rules onto the fields. So even though the data types are the same, and even though the parent API is used to set those fields, those fields still might not be acceptable as values of the child. If LabVIEW allowed any VI to simply cast from parent to child, there would be no way for you, the programmer, to enforce rules about the consistency of data that are specific to a child class.

You may read this and think, "Fine, don't let *any* VI do this cast. Just give the ability to the child VIs to take a parent object and downcast it." But the parent class may have some weird rules. One of its fields might be a refnum, and when ever you try to set it in a new object, a new refnum is allocated, or something like that.

And so, no, LabVIEW cannot provide this arbitrary construction under any circumstances.

Link to post

Thank you all for your responses. Aristos Queue gives a very good explanation of why it is not legal to directly cast a parent class object to a child class object. The workaround is to essentially create a new object and copy the attributes of the old object (doing whatever manipulation of attributes is required in order make those attributes legal for the child class) one-by-one to the new object. That is essentially what Daklu's example does (thanks for that!). I think I was unsuccessful doing the same thing because I tried to copy the elements from the old to new class all inside the same VI, instead of getting the elements in one VI and passing them in as parameters to another VI.

So creating an entirely new object seems like a reasonable way to solve my problem, but the act of transfering the parent attributes to the child attributes is unsatisfying, potentially memory-intensive, and error-prone. Each time I change a parent attribute, I have to modify the code that creates a new child from a parent. So what I really want to do is copy the parent attributes as a group to the new child object, despite the reasons that it is not strictly correct to so. I accept this can't be done, and I do not dispute the reasons, so my question now is: is creating a new object and copying the parent attributes to the child the best way to accomplish my goal? Or is there a better way? I would expect that others have to deal with the same issue I do--how do they deal with this? Perhaps I am thinking about this all wrong?

If creating a new object is indeed the best approach, I have a second question: how would I do this using GOOP (GDS 3)? I see how to do it using LVOOP. With GOOP, I'm sure this could be easily done. I would presumably destroy the parent object and create a child object. On the one hand, I would want the refnum of the new class to be the same as that of the old class, so that any other code referencing the object would still be pointing to the same object after the "downcast". On the other hand, changing the class of an object has to change the data type of the reference, so this sounds impossible. Is there some trick to using the "cast to more specific" in the GOOP Kernel VI?

Link to post

QUOTE (Jeff @ Dec 21 2008, 11:14 AM)

So creating an entirely new object seems like a reasonable way to solve my problem, but the act of transfering the parent attributes to the child attributes is unsatisfying, potentially memory-intensive, and error-prone. Each time I change a parent attribute, I have to modify the code that creates a new child from a parent. So what I really want to do is copy the parent attributes as a group to the new child object, despite the reasons that it is not strictly correct to so. I accept this can't be done, and I do not dispute the reasons, so my question now is: is creating a new object and copying the parent attributes to the child the best way to accomplish my goal? Or is there a better way? I would expect that others have to deal with the same issue I do--how do they deal with this? Perhaps I am thinking about this all wrong?

The solution I would suggest (for minimum code changes AND minimum data copies) is as follows:

  1. create a protected-scope member VI named "Set Parent Values.vi" in the parent class that takes two Parent objects in and puts one Parent object out. Call them Source Object in, Destination Object in, and Destination Object out. Make the Destination Object in be a dynamic dispatch input and the Destination Object out be a dynamic dispatch output.
  2. On the block diagram of this VI, drop an Inplace Element Structure. Create two Bundle/Unbundle pairs on the border of this node and unbundle both the Source and Destination objects. Grow the terminals so that all the elements of the class are unbundled.
  3. Inside the structure, drop N "Swap Inputs" primitives. Wire one element of the unbundle to each of these nodes, and then wire the results to the other side of the structure node.

What have you built? This is the single VI that you need to edit when you change the private data of the parent class. It provides a mechanism for child classes to override the standard behavior.

There's a lot of other tidbits in here. Check out the attached files... unzip and open Demo.vi.

http://lavag.org/old_files/post-5877-1229884239.zip'>Download File:post-5877-1229884239.zip

Link to post

QUOTE (Jeff @ Dec 20 2008, 10:39 AM)

...not really sure how I would go about this using GOOP (I'm using GOOP Developer Suite 3). I see that there is a function for "cast to more specific" in the GOOP Kernel VI, but I'm not sure how to use it (if it should be used).

Hi Jeff

FYI the "cast to more specific" don't work in LVCLasses, it does work in the GOOP2 version of classes and is still in there to be backwards campatible.

I used this function in the UML modeller when I cloned an object. I copied a Java design where you first call the Super.clone method that goes all the way down to the "root" Object-class that everything inherits from in Java.

Then when that method return a new cloned object, I type cast it to more specific and then add "my stuff to it" and then let the sub classes do the same thing.

Cheers,

Mikael

Link to post

Aristos Queue, thanks for that information. This will at least solve the problem about memory duplication when creating a new object. I haven't actually built anything yet. Without your example, I would have done something along the lines of what Daklu built, where a VI to create the new object has inputs for all the attributes from the parent object, and unbundles the old object's attributes and wires them into the new object's attributes using a bundle function.

I'm still considering whether to do use a by-value or by-reference model. So I'd like to know if there is a way to replace a parent object with a child object and still keep the same reference. Mikael? Can I do that in GDS?

Link to post

QUOTE (Jeff @ Dec 22 2008, 08:00 AM)

I'm still considering whether to do use a by-value or by-reference model. So I'd like to know if there is a way to replace a parent object with a child object and still keep the same reference.
Why are you having to choose between by value and by reference? Say it with me:

  • By ref and by value models are NOT MUTUALLY EXCLUSIVE.
  • By ref and by value models are NOT MUTUALLY EXCLUSIVE.
  • By ref and by value models are NOT MUTUALLY EXCLUSIVE.

You should be using references for classes that represent hardware and system resources and, sometimes, for extremely large data structures if you're not confident of your ability to avoid forking wires to modifier functions and thus creating data copies. You should be using data for classes that represent data, including very large data structures where you are confident of your ability to write LV code that avoids data copies. My personal estimate -- based on two years of observing programs from LV developers, both inside NI and not -- is that

  1. The vast majority of programs have no use for references whatsoever
  2. The total number of classes that should be by reference in the world is roughly 5% of the total of classes written.

The GOOP Toolkit refnums are -- get this -- written as by-value classes. They are by-value classes that implement reference functionality. Using one domain does not exclude using the other. To put it another way for C++ programmers: Using a class pointer in one part of your code does not prevent you from declaring local variables of the class in another part of your code.

Link to post

Oops, I didn't mean to revive the by-ref/by-value debate. Sorry! Perhaps this should be the start of a separate thread, but let me explain what I am trying to do (at the risk of exposing myself to more wrath). I am creating a TestStand UUT dialog that runs in parallel to multiple UUT executions. The dialog acts on user interface events, updates the states of UUTs based on the events, and updates the display based on the states of the UUTs. In the meanwhile, the UUT executions are also getting and updating UUT states. So all the parallel threads require simultaneous access to the same objects.

Perhaps I am old-school and too used to a class with a protected data repository that can be shared among parallel pieces of code, but this seems like an obvious solution to me. I'm completely in favor of avoiding references (and I have been trying to figure out a way to do so) if you can show me the error in my ways.

Link to post

QUOTE (Jeff @ Dec 22 2008, 09:57 AM)

Perhaps I am old-school and too used to a class with a protected data repository that can be shared among parallel pieces of code, but this seems like an obvious solution to me. I'm completely in favor of avoiding references (and I have been trying to figure out a way to do so) if you can show me the error in my ways.
The easiest thing to do in this case is ask yourself, "If my data was an integer, just an integer, how would I structure this?" If the answer is still, "I would create a reference to it" then a reference is the right thing to do for the class. But the answer may very well be "I would add parameters to my subVIs and pass the integer to them" or "I would create a functional global that manages that integer" or "I would use a global VI to store that integer".

But, again, the decision applies to that one class only, and you would then make the same evaluation for the next class in line.

Link to post

For my case, I think my answer would be "I would create a functional global that manages the <class data>" (and not, "I would create a reference to it"). But isn't that what GOOP effectively is? It's a functional global that manages data, with additional mechanisms to manage multiple data sets.

Link to post
QUOTE (Jeff @ Dec 22 2008, 10:33 AM)
For my case, I think my answer would be "I would create a functional global that manages the <class data>" (and not, "I would create a reference to it"). But isn't that what GOOP effectively is? It's a functional global that manages data, with additional mechanisms to manage multiple data sets.
Yes, it is, and thus, in this case, references seem like an acceptable choice.

Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.