3

Consider the following example:

public class MyParentClass {
   public int MyInt { get; set; }
}

public class MyChildClass : MyParentClass
{
}

public class AnotherClass
{
   public MyChildClass GetChildClassFromParentClass(MyParentClass parent)
   {
       return new MyChildClass() { MyInt = parent.MyInt };
   }
}

I'm wondering why it's not possible to directly clone the parent class into it's child without the manual step of copying all its values, since MyParentClass and MyChildClass share this 'relationship'.

Maybe such a feature would not be able to guarantee the consistency of MyChildClass because of missing injected dependencies, but the this language feature could at least permit this cloning if there is a parameterless constructor defined for the child.

Let me be clear I know this feature is not a good idea and there definitely are scenarios which make this a language feature that isn't feasible or safe. But I'd like to know what those scenarios are.

Minor note: I believe this is not an opinion-based question, since no mainstream OOP language seems to offer this feature, so there must be objective arguments against it.*

  • Opinion; a view or judgement formed about something, not necessarily based on fact or knowledge. I'm not interested in a view or judgement, and I'd like a factual answer, so I don't think it's an opinion I'm after (as stated). I'd like example scenarios of why this language feature isn't do'able. – JᴀʏMᴇᴇ Jan 12 '18 at 16:44
  • 3
    What problem are you imagining that this 'feature' would solve? Language designers tend to try to keep languages as simple as possible, so features are not added unless their value is worth the additional complexity, and personally I'm struggling to see the value in this feature. – Sean Burton Jan 12 '18 at 16:54
  • 1
    I'm not certain that anything can't be a language feature. Many things are undesirable as language features, and many other things are not desirable enough to merit the cost of implementing them in a given language – Caleth Jan 12 '18 at 17:31
  • 1
    Voting to close for being too broad. There may well be a reason that particular languages don't do this. But, I don't think it'd be accurate to generalize about all OOP languages -- even if we have evidence of this having been an actual decision in the development of any language.... If that makes sense. – svidgen Jan 14 '18 at 21:40
8

Is it possible to easily clone a parent into a child ?

Some languages allow this by specifying the base class constructor. Here in C++:

class MyParentClass {
public: 
    int MyInt;
};

class MyChildClass : public: MyParentClass
{
public: 
    MyChildClass () = default; 
    MyChildClass (const MyParentClass& x) : MyParentClass(x) {}  // <== you need to tell   
};

The constructing logic would first construct the parent by using a copy construction based on x. So everything in the parent would be copied (including private values). Of course, if the parent would have some non-copiable member, this would fail at compilation.

Your member function in the other class would look like:

MyChildClass GetChildClassFromParentClass(MyParentClass parent)
{
   return MyChildClass(parent);
}

Demo

Are there reasons not to provide this behavior by default ?

Semantically, this doesn't always make sense. Take the example of a child Square and an abstract parent Shape: the shape has in principle not enough informations to instantiate a Square.

Downcasting from a general class into a more specialized is often looked at with some suspicion. And copying by default an parent class into a child class would be a kind of downcasting by default.

Moreover such a language feature combined with other existing features would be a big source of error. If today you'd write something like:

 Shape x; 
 Circle c = new Circle(radius, center);  
 Square s; 
 x = c;  // ok - polymorphism would do 
 s = c;  /// OUCH !!!

You would get a type mismatch error because it's insane to assign a circle to a square.

But if your "parent to child" feature would be enabled by default in the language, the statement could compile:

 s = c;   // would compile but with a different semantic 

Indeed c is a Shape and a Shape could be copied into a Square according to your desired feature. And I'd bet that there are big chances that in most of the cases this would just be plain wrong and the mismatch should be brought to the attention of the programmer at compile time !

4

What you describe can be done through constructors.

return new MyChildlClass(10);

MyChildClass should then provide a constructor that call's it's parent constructor:

public class MyChildClass : MyParentClass
{
    public MyChildClass(int myInt) : base(myInt);
}

Then, you can myChildClass.MyInt since you marked it as public.

If you wanted a parameter less constructor for the child, you could do something like this:

 public MyChildClass() : base(10);

So, now we have default parent/child behavior/state, otherwise we use the other constructor to define the behavior/state for the parent.

  • Thanks very much for your answer Jon. If I'm not mistaken, given this code example, wouldn't MyParentClass's constructor still have to 'map' the myInt parameter value over to MyInt 'manually'? Or is there a language feature here I'm not aware of? Thanks again – JᴀʏMᴇᴇ Jan 13 '18 at 9:27
  • Maybe in some languages, but in C++ you get an auto-generated copy-constructor which does a full member-wise copy of the class without having to define this yourself. – Sean Burton Jan 15 '18 at 10:43
  • @Jaymee - Yes, calling : base(10) would require the parent constructor to include an assignment in it's constructor logic as well. – Jon Raynor Jan 15 '18 at 17:51
3

If you could clone a base class into a child class, then you could cast anything as anything. You could cast a List<int> as object and then clone the object into a ConcurrentQueue<HttpRequest> or an Exception.

Here's an example that shows a problem cloning a base into its child:

public class ClassWithNoValue
{

}

public class ClassWithValue : ClassWithNoValue
{
    public int Value { get; private set; }

    public void SetValue(int value)
    {
        if (value > 0) throw new ArgumentException($"{nameof(value)} must be negative!");
        Value = value;
    }
}

If you could clone ClassWithNoValue into ClassWithValue you would bypass the control within ClassWithValue that prevents an invalid value. Value would be zero, because what else would it be other than the default? We write classes to ensure that they can't enter an invalid state, but such "upcloning" would go around that.

It helps if we visualize the relationship between base classes differently. Externally the relationship is more pronounced because a class can be downcast to a base class.

But other than that they are entirely separate classes. A base class hides its internals from its inherited classes, and vice versa. The only difference in relationship between inherited classes is that a base class can choose to share certain details only with its inherited classes.

Even abstract and virtual methods don't really share anything between classes. For a base class to have such methods is essentially the same as a base class that requires certain dependencies supplied in its constructor so that it can't be instantiated without them. Virtual methods are like optional constructor arguments with internally specified defaults.

0

Fundamentally, there's no reason this couldn't be done. To work as most people would normally expect, you'd have to use a copy-on-write (COW) strategy.

That is, you have two children, each of which we expect to have what acts like its own copy of the parent object. As long as the parent objects of both contain the same values, the two can share access to the same object. Even though we conceptually have two separate objects, they both contain the same values, so accessing the same object (mostly--see below) acts the same as if we had two separate objects.

When one of the child objects attempts to change part (or all) of the data in the parent object, we have a problem though--if both children contain references to a common parent, changing a parent value in one child will also change that value in the other child--definitely not what we'd normally expect. To deal with that, we create separate copies of the parent in each child if and only if/when one or the other writes to the parent sub-object--that is, if one modifies a part from the parent, then (and only then) we create two separate copies, and modify one (but leave the other alone).

There are still some possible problems with this though. One deals with timing. Somebody might expect that creating a new object is fairly slow, and assigning to (for example) an int is fast. If we use copy on write, creating the new object can be faster, but the first assignment (even the most trivial) to the parent-part of a child object can be almost arbitrarily slow (i.e., can involve copying the entire parent object, which could be quite large).

Another possible violation of expectations could arise if you had two child objects being used in separate threads, and both sharing access to a single parent object. Just for example, on a NUMA machine, a user might expect that each thread would have "local" access to its parent object, but if the two threads were on separate nodes one could end up with a slower access to a remote node instead.

At the same time, I don't think such a feature would necessarily need to be reflected in the language specification. A language specification normally places requirements on the observable behavior of programs. The point of using something like a COW strategy would be that the observable behavior of a program is not affected. It's purely an implementation strategy that has no affect on the interface. The potential "problems" cited above (and possibly a few others) are basically examples of potential conflicts between what a language specification might define as "observable" behavior, and behavior a reasonably normal user of that language might think qualifies as "observable".

0

no mainstream OOP language seems to offer this feature, so there must be objective arguments against it.

Not necessarily. Languages are not designed by starting from a pool of "all possible features" and explicitly remove the features which are considered problematic. Rather, language design typically starts from a minimal core or from a previous language, and only new features which are considered to provide significant values are added.

So it is quite possible nobody really thought about adding this feature in the first place.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.