简体   繁体   中英

Avoiding object slicing and using shared_ptr

Now I'm developing a searching node program. My code is below and I actually want to search a specific child node. To avoid object slicing, I used a pointer, but the pointer is null_ptr maybe. So how should I avoid this problem. I'm not sure its problem? And to avoid new instance attribute, Parent obj instance in main() should be declared as shared_ptr supported since c++11?

#include <iostream>

 using namespace std;

 class Parent {
   public:
     Parent() { name = "Parent"; }
     virtual void print() {
       cout << "Parent::print()" << endl;
     }

     string name;
 };

 class Child1 : public Parent {
   public:
     Child1() { name = "Child1"; }
     virtual void print() override{
       cout << "Child1::print()" << endl;
     }
 };

 class Child2 : public Parent {
   public:
     Child2() { name = "Child2"; }
     virtual void print() override{
       cout << "Child2::print()" << endl;
     }
 };

 class Manager {
   public:
     void getChild(int i, Parent* obj) {
       if(i==0) {
         cout << "sest child1" << endl;
         obj = (&child1);
       } else {
         cout << "sest child2" << endl;
         obj = (&child2);
       }
     }
     Child1 child1;
     Child2 child2;
 };

 int main()
 {
   // object slicing?
   // Parent obj;
   // Manager manager;
   // manager.getChild(1, obj);
   // obj.print();

   Parent* obj;
   Manager manager;
   manager.getChild(1, obj);
   obj->print();

 }

My code is broken because of segmentation fault.

  $ a.out D
    sest child2
    [1]    5457 segmentation fault  ./work/derived/a.out D
void getChild(int i, Parent* obj)

This function signature is telling us that the function takes an integer and pointer to an object. Both are passed by value:

If you have an object, say at address 42, then you would effectively pass the number 42 as second parameter.

Inside the function the parameters act as variables, and since they're not const in this case, you can modify them. So when you wrote

obj = &child1;

you only changed the state of a local variable, say from the aforementioned 42 to some other address, eg 24. None of this is affecting the pointer the caller gave as second parameter.

What you wanted to achieve is having an "out" parameter. For that you need a pointer to the thing you want to change. In your case you want to change a pointer, so you need a pointer to a pointer:

void getChild(int i, Parent * * obj_ptr)

To set it to a value, you need to dereference it:

*obj_ptr = &child1;

But in this case I wouldn't use an out parameter, since you're able to return a value from functions, so just return the pointer:

Parent* getChild(int i) {
  // ...
  return &child1;
}
// in main
Parent* node = manager.getChild(1);

concerning std::shared_ptr : You should think about who owns the instances your pointers point to. That is, who is responsible of destructing them and freeing the associated memory. In your case the child instances are part of the manager instance. So the manager instance owns them and will take care of them. A shared_ptr is for the (hopefully) rare cases when you don't exactly know how long the instance is needed. So you share the ownership, making the last owner responsible of destructing and freeing.

What kind of ownership you actually need here is not entirely clear to me. Do you want some kind of traversal, Ie are the child instances parents (or managers in your terminology) of other children? Or do you just need access to the members? Then I wouldn't use pointers at all but references.

You are passing the pointer obj to getChild by value (taking a copy). If you change the local obj pointer to point to a different object that doesn't change obj pointer in the main scope. The obj pointer in the main scope still points to garbage.

As obj is an out parameter a more idiomatic way of writing getChild would be to use a return value:

Parent* getChild(int i) {
  if(i==0) {
    cout << "sest child1" << endl;
    return &child1;
  }
  cout << "sest child2" << endl;
  return &child2;
}

Which you can use like this:

Manager manager;
auto obj = manager.getChild(1);
obj->print();

And no, you shouldn't use a shared_ptr for obj in the main scope. At least not as it is written now. Currently, child1 and child2 are owned by Manager as they are by-value member variables. You don't want anyone else deleting them other than Manager .

But perhaps that was not the design you intended. Perhaps you intended something more like the Prototype Pattern where getChild returns a clone of child1 or child2 which should be owned by the caller.

I think you shouldn't allocate child1 and child2 on stack , rather you should allocated it on head using new operator .Second modification would be to user pointer to pointer while getting any new memory allocation in some different function , Modified program is below :

    #include <iostream>
#include <string>

 using namespace std;

  class Parent {
         public:
                  Parent() { name = "Parent"; }
                       virtual void print() {
                                  cout << "Parent::print()" << endl;
                                       }

                            string name;
                             };

 class Child1 : public Parent {
        public:
                 Child1() { name = "Child1"; }
                      virtual void print() {
                                 cout << "Child1::print()" << endl;
                                      }
                       };

 class Child2 : public Parent {
        public:
                 Child2() { name = "Child2"; }
                      virtual void print() {
                                 cout << "Child2::print()" << endl;
                                      }
                       };

 class Manager {
        public:
                 Manager()
                          {
                                 child1 = new Child1;
                                     child2 = new Child2;
                                          }
                      void getChild(int i, Parent** obj) {
                                 if(i==0) {
                                              cout << "sest child1" << endl;

                                                 *obj = child1;
                                                        } else {
                                                                     cout << "sest child2" << endl;

                                                                         *obj = child2;
                                                                                }
                                      }
                           Child1 *child1;
                                Child2 *child2;
                                 };

 int main()
     {

            Parent* obj;
               Manager manager;
                  manager.getChild(1, &obj);
                     obj->print();

                      }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM