Why can't a derived class pointer point to an object of a base class without casting?

I saw a few examples like Pet and Dog for this basic question here and here , but for me it doesn't make sense, that's why.

Suppose we have the following class structure

class Pet {}; class Dog : public Pet {}; 

then the following statement

a (Dog) is a (Pet)

may be true in real life, but, in my opinion, is not true in C ++ . Just look at the logical representation of the Dog object, it looks like this:

enter image description here

It’s more appropriate to say

a (Dog) has a (Pet)

or

a (Pet) is a subset of (Dog)

which, if you notice, is the logical opposite of "The dog is a pet"


Now the problem is that # 1 below is resolved, and # 2 is not:

 Pet* p = new Dog; // [1] - allowed! Dog* d = new Pet; // [2] - not allowed without explicit casting! 

My understanding is that [1] not allowed without warnings, because the pointer cannot point to an object of its superset type (the Dog object is a subset of Pet) simply because Pet does not know anything about the new members that Dog could declare (a subset of Dog - Pet in the diagram above).

[1] equivalent to int* trying to point to a double object!

It is obvious that I do not have a key point that will turn all my arguments upside down. Could you tell me what it is?

I believe that parallels with real examples only complicate the situation. I would rather understand this in terms of technical details. Thanks!

+6
source share
11 answers

Edit: re-reading my question, and my answer makes me say this at the top:

Your understanding of is a in C ++ (polymorphism in general) is wrong.

A is B means A has at least the properties of B, possibly more , by definition .

This is consistent with your statements that Dog has Pet and that [attributes] of Pet are [are] a subset of [attributes] of Dog .


It is a matter of defining polymorphism and inheritance. The drawings you draw are aligned with the representation of the Pet and Dog instances in mind, but are misleading in how you interpret them.

 Pet* p = new Dog; 

Pointer p defined as pointing to any Pet-compatible object, which in C ++ is any subtype of Pet (Note: Pet is a subtype of itself by definition). Runtime is guaranteed that when accessing an object beyond p , it will contain everything that Pet should contain, and possibly more . “Perhaps more” is Dog in your diagram. The way you draw your chart gives a false interpretation.

Think about the layout of class members in memory:

 Pet: [pet data] Dog: [pet data][dog data] Cat: [pet data][cat data] 

Now, when Pet *p points out, you need to have a [pet data] and, optionally, something else. From the list above, Pet *p can point to any of the three. As long as you use Pet *p to access objects, you can only access [pet data] , because you do not know what if anything, afterwards. It’s a contract that says It’s at least Pet, maybe more .

What Dog *d indicates must have [pet data] and [dog data] . Thus, the only object in memory that it can reference is a dog. And vice versa, through Dog *d you can access both [pet data] and [dog data] . Similarly for Cat .


Let's interpret the declarations you confuse:

 Pet* p = new Dog; // [1] - allowed! Dog* d = new Pet; // [2] - not allowed without explicit casting! 

I understand that 1 should not be allowed without warning, because the pointer should not point to the object of its subset (the Dog object is a subset of Pet) simply because Pet does not know anything about the new members that Dog (the subset of Dog - Pet in the diagram above).

Pointer p expects to find [pet data] in the place where it points. Since the right side is Dog , and each Dog object has [pet data] in front of its [dog data] , pointing to an object of type Dog , everything is fine.

The compiler does not know what is behind the else pointer, so you cannot access [dog data] via p .

Declaration is allowed because the presence of [pet data] can be guaranteed by the compiler at compile time. (this statement is obviously simplified from reality to fit the description of your problem)

1 is equivalent to int * trying to point to a double object!

There is no such subtype relationship between int and double as between Dog and Pet in C ++ . Try not to mix them with discussion because they are different: you add values from int and double ( (int) double Explicit, (double) int implicit), you cannot use pointers between them . Just forget this comparison.

Regarding [2]: the statement says: " d points to an object with [pet data] and [dog data] , possibly more." But you only [pet data] , so the compiler tells you that you cannot do this.

In fact, the compiler cannot guarantee if everything is in order, and it refuses to compile. There are legitimate situations where the compiler refuses to compile, but you, the programmer, know better. What are static_cast and dynamic_cast ? The simplest example in our context:

 d = p; // won't compile d = static_cast<Dog *>(p); // [3] d = dynamic_cast<Dog *>(p); // [4] 

[3] will always succeed and lead to errors with errors if p is not really Dog .
[4] will return NULL if p not really Dog .

I heartily suggest trying these exiles to see what you get. You should get garbage for [dog data] from the static_cast pointer and NULL for dynamic_cast , assuming RTTI is enabled.

+26
source

A dog is a pet because it comes from the Pet class. In C ++, which pretty much satisfies the OOP requirement. What is the Liskov substitution principle?

 Dog* d = new Pet;  // [2] - not allowed without explicit casting! 

Of course, this is forbidden, a pet can be just a cat or a parrot.

+4
source

This is a problem of hierarchical classification. If I tell my children that they can have a pet, then the dog is definitely allowed. But if I tell them that they can only have a cat, then they cannot ask for fish.

+4
source

In terms of technical details:

Additional information of the Dog object is added to the end of the Pet object, so the prefix [in bits] of Dog is actually Pet , so there is no problem assigning Dog* an object with the variable Pet* . It is completely safe to access any of the Pet fields / methods of the Dog object.

However - oposite is not true. If you assign the address to the variable Pet* a Dog* and then gain access to one of the Dog [fields, which is not in Pet ], you will exit the selected location.

Text entry:
Also note that a value can only be assigned to a variable if it has the correct type without casting [C ++ is the static typing of langauge], since Dog is Pet , Dog* is Pet* - so this does not contradict, but vice versa - incorrect .

+3
source

I think you are confusing what is - a, which should mean in the context of OO. You can say that Dog has a Pet sub-object, and it’s true if you go on to represent bits in objects. But the important thing is that programming is a simulation of reality in a program that a computer can process. Inheritance is the way you model the relationship: a, as in your example:

A Dog is-a Pet

In a common language, it means that it demonstrates all the pet’s behavior, perhaps some characteristic behavior that is different (barks), but it is an animal, and it provides a company, you can feed it ... All these behaviors will be determined (virtual ) members in the Pet class and can be overridden in the Dog type, like other operations. But the important part is that, using inheritance, all instances of Dog can be used where intensive Pet is required.

+2
source

You are confused between the base and parent classes.

Pet is the base class. A Pet* can indicate any number of different types if they inherit from Pet . Therefore, it is not surprising that Pet* pet = new Dog allowed. Dog - Pet . I have a pointer to Pet , which is Dog .

On the other hand, if I have Pet* , I have no idea what this indicates. It can point to Dog , but it can also point to Cat , a Fish or something else. Thus, the language will not allow me to call Pet->bark() , because not all Pet can bark() ( Cat meow() , for example).

If, however, I have Pet* , which I know is essentially Dog , then it is completely safe to be added to Dog , and then calls bark() .

So:

 Pet* p = new Dog; // sure this is allowed, we know that all Dogs are Pets Dog* d = new Pet; // this is not allowed, because Pet doesn't support everything Dog does 
+1
source

In English, the statements you are trying to solve may be "aspects of the dog that calls it like a pet, are a subset of all aspects of the dog" and "the totality of all entities that are dogs are a subset of many entities that are pets."

D, P such that D (x) => x & isin; Dog, P (x) => x & isin; Pet

D (x) => P (x)

(D (x) is true if x has all aspects of the dog, so this suggests that aspects of the thing that is the dog are a super-set of aspects of things that are pets - P (x) is true if D ( x) true, but not necessarily the opposite)

Dog & supe; Pet =>

&for all; xx & isin; Dog => x & isin; Pets (each dog is a pet)

But if D (x) & equiv; x & isin; Dog, then this is one and the same statement.

Thus, “the aspects of the dog that make it a pet are a subset of the dog as a whole,” equivalent to “the set of dogs is a subset of many pets”

0
source

Consider this scenario (sorry for such a crappy example):

A car can be any vehicle

 class Vehicle { int numberOfWheels; void printWheels(); }; 

A car is a car

 class Car: public Vehicle { // the wheels are inherited int numberOfDoors; bool doorsOpen; bool isHatchBack; }; 

A bicycle is a vehicle, but this car is not a car, which is also a vehicle.

 class Bike: public Vehicle { int numberOfWings; // sorry, don't know exact name // the wheels are inherited }; 

Therefore, I hope that you will not only see the real difference in life, but also notice that the memory layout in the program will be different for Bike and Car objects, although they are both Vehicles . Therefore, a child cannot be a type of child; it can only be what has been determined.

0
source

All of the above answers are good. I just want to add one more thing. I think your dog / pet chart is misleading.

I understand why you sketched a DOG diagram as a superset of PET: you probably thought that since the dog has more attributes than the pet, it needs to be represented in a large set.

However, whenever you draw a diagram where the set B is a subset of the set A, you say that the number of objects of type B is probably LONGER than objects of type A. On the other hand, since objects of type B have more properties, you allowed to perform more operations with them, since you can do ALL the operations allowed for objects of type A PLUS, a little more.

If you know something about functional analysis (this is a long shot, but you may have seen it), this is the same relationship that exists between Banach spaces and their counterparts: the smaller the space, the larger the set of operations that can be done on them .

0
source

The actual reason is that the derived class has all the information about the base class, as well as some extra bit of information. Now a pointer to a derived class will require more space and this is not enough for the base class. Thus, a pointer to a derived class cannot point to it. The converse is true, on the other hand.

0
source

Based on some previous answers, I understood some understanding, and I put them in my words. Based on some previous answers, I developed some understanding, and I put them in my words

0
source

Source: https://habr.com/ru/post/911000/


All Articles