C ++ Learning: Link Returns and Search

I have a devil of understanding of time. Consider the following code:

class Animal { public: virtual void makeSound() {cout << "rawr" << endl;} }; class Dog : public Animal { public: virtual void makeSound() {cout << "bark" << endl;} }; Animal* pFunc() { return new Dog(); } Animal& rFunc() { return *(new Dog()); } Animal vFunc() { return Dog(); } int main() { Animal* p = pFunc(); p->makeSound(); Animal& r1 = rFunc(); r1.makeSound(); Animal r2 = rFunc(); r2.makeSound(); Animal v = vFunc(); v.makeSound(); } 

And the results: "bark bark rawr rawr".

In Java thinking (which apparently messed up my C ++ conceptualization), the result would be "bark bark bark". I understand from my previous question that this difference is related to cutting, and now I understand well what cutting is.

But let's say that I need a function that returns the value of Animal, which really is a Dog.

  1. Do I understand correctly that the closest I can get is a link?
  2. In addition, he must use one that uses the rFunc interface to see that the returned link assigns Animal &? (Or else deliberately assign a reference to the Animal, which throws out polymorphism by slicing.)
  3. How should I return a link to a newly created object without doing the stupid thing I did above in rFunc? (At least I heard it was stupid.)


Update: since everyone seems to still agree that rFunc is illegal, which raises other related issues:

If I pass a pointer, how can I tell the programmer that the pointer does not delete them, if so? Or, as an alternative, how can I tell you that the pointer can be deleted at any time (from the same thread, but with a different function) so that the calling function does not save it, if so. Is this the only way to communicate this through comments? It seems sloppy.

Note. All this leads to the idea of ​​the template concept shared_pimpl, on which I worked. Hopefully I find out enough to post something about this in a couple of days.

+11
c ++ polymorphism reference object-slicing
Dec 10 '10 at 4:22
source share
11 answers

1) If you create new objects, you will never want to return a link (see your own comment on # 3.) You can return a pointer (possibly wrapped std::shared_ptr or std::auto_ptr ). (You can also return a copy, but this is incompatible with the new operator, and also slightly incompatible with polymorphism.)

2) rFunc simply incorrect. Do not do this. If you used new to create the object, return it via the (optionally wrapped) pointer.

3) You should not. For this we need pointers.




EDIT (response to your update :) It is difficult to imagine the scenario that you are describing. Would it be more accurate to say that the returned pointer may not be valid as soon as the caller calls some other (specific) method?

I would advise against using such a model, but if you absolutely must do this, and you must apply this in your API, then you probably need to add a level of indirection or even two. Example. Wrap the real object in a reference counting object that contains a real pointer. A reference counting object pointer is set to null when a real object is deleted. This is ugly. (There may be better ways to do this, but they can still be ugly.)

+5
Dec 10 '10 at 4:35
source share
— -

To answer the second part of your question ("how can I tell you that the pointer can be deleted at any time") -

This is a dangerous practice and contains subtle details that you will need to consider. This is a decent character.

If a pointer can be deleted at any given time, never use it from a different context, because even if you check, "are you still true?" each time it can only be deleted one bit after checking, but before you use it.

A safe way to do this is the concept of a "weak pointer" - to have an object that will be stored as a shared pointer (one level of indirection, can be released at any time), and the return value - a weak pointer is what you need to request before you can use, and should exit after you use it. So while the object is still valid, you can use it.

Pseudocode (based on invented weak and general pointers, I do not use Boost ...) -

 weak< Animal > animalWeak = getAnimalThatMayDisappear(); // ... { shared< Animal > animal = animalWeak.getShared(); if ( animal ) { // 'animal' is still valid, use it. // ... } else { // 'animal' is not valid, can't use it. It points to NULL. // Now what? } } // And at this point the shared pointer of 'animal' is implicitly released. 

But it is difficult and error prone, and most likely your life will become more complicated. I would recommend, if possible, simpler projects.

+2
Dec 10 '10 at 5:15
source share

To avoid slicing, you need to return or pass a pointer to the object. (Note that the link is basically a "persistent dereferenced pointer".

 Animal r2 = rFunc(); r2.makeSound(); 

Here r2 is instantiated (using the compiler generated instance of ctor), but it gets away from the parts of the dog. If you do this like this, slicing will not happen:

 Animal& r2 = rFunc(); 

However, your vFunc () function slices inside the method itself.

I also mention this function:

 Animal& rFunc() { return *(new Dog()); } 

It is strange and unsafe; you create a link to a temporary unnamed variable (Dereferenced Dog). It is more appropriate to return the pointer. Returned links are usually used to return member variables, etc.

+1
Dec 10 '10 at 4:40
source share

If I pass a pointer, how can I tell the programmer that the pointer does not delete them, if so? Or, as an alternative, how can I tell you that the pointer can be deleted at any time (from the same thread, but with a different function) so that the calling function does not save it, if so.

If you really can’t trust the user, don’t give them a pointer at all: pass an integer type descriptor and you will see a C-style interface (for example, you have an instance vector on your side of the fence and expose a function that takes an integer as the first parameter indexes into a vector and calls a member function). This is old fashioned (despite the fact that we did not always have such fantastic things as "member functions";)).

Otherwise, try using a smart pointer with the appropriate semantics. None of reasonable people will ever think that delete &*some_boost_shared_ptr; - a good idea.

+1
Dec 10 '10 at 6:02
source share

But let's say that I need a function that returns the value of Animal, which really is a Dog.

  • Do I understand correctly that the closest I can get is a link?

Yes you are right. But I think the problem is not that you don’t understand the links, but that you don’t understand the different types of variables in C ++ or how new works in C ++. In C ++, variables can be primitive data (int, float, double, etc.), an Object, or a pointer / reference to a primitive and / or object. In Java, variables can only be a primitive or an object reference.

In C ++, when you declare a variable, the actual memory is allocated and associated with the variable. In Java, you must explicitly create objects using new ones and explicitly assign the new object to a variable. The key point here is that in C ++, the object and variable that you use for access is not the same thing when the variable is a pointer or reference. Animal a; means something other than Animal *a; , which means something different from Animal &a; . None of them have compatible types, and they are not interchangeable.

On input, Animal a1 in C ++. A new Animal object is created. So, when you type Animal a2 = a1; , you get two variables ( a1 and a2 ) and two Animal objects elsewhere in memory. Both objects have the same value, but you can change their values ​​independently if you want. In Java, if you type the exact same code, you will get two variables, but only one object. Until you reassign any of the variables, they will always have the same value.

  • In addition, he must use one that uses the rFunc interface to see that the returned link assigns Animal &? (Or else deliberately assign a reference to the Animal, which throws out polymorphism by slicing.)

When you use references and pointers, you can access the value of an object without copying it to where you want to use it. This allows you to modify it from outside the curly braces where you declared the existence of the object. References are usually used as function parameters or to return members of the private data of an object without a new copy. As a rule, when you receive a link, you do not assign it to anything. Using your example, instead of assigning the link returned to the rFunc() variable, you should usually enter rFunc().makeSound(); .

So, yes, the user rFunc() must, if they assign a return value to something, assign it a link. You can understand why. If you assign the link returned by rFunc() variable declared as Animal animal_variable , you get one Animal variable, one Animal object and one Dog object. The Animal object associated with animal_variable is, as far as possible, a copy of the Dog object that was returned by reference from rFunc() . But you cannot get polymorphic behavior from animal_variable , because this variable is not related to the Dog object. The Dog object that was returned by the link still exists because you created it with new , but it is no longer available - it leaked.

  • How should I return a link to a newly created object without doing the stupid thing I did above in rFunc? (At least I heard it was stupid.)

The problem is that you can create an object in three ways.

 { // the following expressions evaluate to ... Animal local; // an object that will be destroyed when control exits this block Animal(); // an unamed object that will be destroyed immediately if not bound to a reference new Animal(); // an unamed Animal *pointer* that can't be deleted unless it is assigned to a Animal pointer variable. { // doing other stuff } } // <- local destroyed 

Everything new in C ++ creates objects in memory where they will not be destroyed until you say so. But in order to destroy it, you must remember where it was created in memory. You do this by creating a pointer variable, Animal *AnimalPointer; , and assigning it the pointer returned by new Animal() , AnimalPointer = new Animal(); . To destroy an Animal object, when you are done with it, you must enter delete AnimalPointer; .

+1
Dec 16 '10 at 22:21
source share

(I ignore your problems with dynamic memory falling into links causing memory leaks ...)

Your splitting problems disappear when Animal is an abstract base class. This means that it has at least one pure virtual method and it cannot be directly created. The following becomes a compiler error:

 Animal a = rFunc(); // a cannot be directly instantiated // spliting prevented by compiler! 

but the compiler allows you to:

 Animal* a = pFunc(); // polymorphism maintained! Animal& a = rFunc(); // polymorphism maintained! 

Thus, the compiler saves the day!

0
Dec 10 '10 at 4:44
source share

Point 1: do not use links. Use pointers.

Point 2: the thing you have above is called a taxonomy, which is a hierarchical classification scheme. Taxonomies are an example of this kind, which is completely unsuitable for object-oriented modeling. Your trivial example only works because your Animal base assumes that all animals are noisy and cannot do anything interesting.

If you try to implement a relationship, for example

virtual bool Animal :: eats (Animal * other) = 0;

You will find that you cannot do this. The fact is that a dog is not a subtype of animal abstraction. The whole point of taxonomies is that classes at each level of a section have new interesting properties.

For example: vertebrates have a spine, and we may ask if it is made from a hook-up or bone. We cannot even ask this question about invertebrates.

To fully understand, you should see that you cannot create a Dog object. After all, this is an abstraction, isn't it? Because there is Kelpie and Collie, and a separate Dog must be of some species. The classification scheme may be as deep as you like, but it will never be able to support specific people. Fido is not a dog , this is just his classic tag.

0
Dec 10 '10 at 4:45
source share

If you want to return a polymorphic type from a method and do not want to assign it on the heap, you can consider creating a field in this class of methods and force the function to return a pointer to it of any base class you want.

0
Apr 17 '13 at 21:38
source share

I put an interesting comment, and the message simply accepted "-1". This is not very kind. Could you at least explain before you dissuade people from such behavior?

0
Jul 20 '19 at 12:47
source share

I was looking for an explanation on this. And after I realized some new things, based on what I already knew.

Dr. Scott Meyers, p. 291 of Effective Modern C ++, writes about the problem of slicing . This problem is solved by the correct application of C ++ polymorphism rules.

I found out that there are 2 rules of polymorphism:

1) using the keyword "virtual" as you did. 2) The second uses a pointer or link. Without this second rule, you will face the problem of slicing.

0
Jul 21 '19 at 10:14
source share

Good afternoon.

Dr. Scott Meyers, p. 291 of Effective Modern C ++, writes about the problem of slicing . This problem is solved by the correct application of C ++ polymorphism rules.

Without declaring a pointer or reference, you get a type that is the base type, even if the return type of your function uses a pointer or reference. It is important to have a pointer or link to declare your return object.

In fact, to avoid errors in your example, when the return type of the function is a pointer, it gives you a type hint to declare the object you want, at least. Without a link or pointer, the type you really want is cut from the part you want to add.

-one
Jul 20 '19 at 11:22
source share



All Articles