避免对象切片和使用shared_ptr

Avoiding object slicing and using shared_ptr

本文关键字:shared ptr 对象 切片      更新时间:2023-10-16

现在我正在开发一个搜索节点程序。我的代码在下面,我实际上想搜索一个特定的子节点。为了避免对象切片,我使用了一个指针,但指针可能是null_ptr。那么我该如何避免这个问题呢。我不确定它的问题是什么?为了避免新实例属性,main()中的父对象实例应声明为自c++11以来支持的shared_ptr?

#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();
 }

由于分段错误,我的代码被破坏了。

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

这个函数签名告诉我们,函数接受一个整数和指向对象的指针。两者都通过值传递:

如果您有一个对象,比如在地址42,那么您将有效地传递数字42作为第二个参数。

在函数内部,参数充当变量,由于在这种情况下它们不是常量,因此可以对它们进行修改。所以当你写的时候

obj = &child1;

您只更改了一个局部变量的状态,例如从前面提到的42更改为其他地址,例如24。所有这些都不会影响调用方作为第二个参数给出的指针。

您想要实现的是拥有一个"out"参数。为此,你需要一个指向你想要改变的东西的指针。在您的情况下,您想要更改指针,因此需要一个指向指针的指针:

void getChild(int i, Parent * * obj_ptr)

要将其设置为一个值,您需要取消引用它:

*obj_ptr = &child1;

但是在这种情况下,我不会使用out参数,因为您可以从函数中返回值,所以只需返回指针:

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

关于std::shared_ptr:您应该考虑谁拥有指针指向的实例。也就是说,谁负责破坏它们并释放相关内存。在您的案例中,子实例是管理器实例的一部分。因此,管理器实例拥有它们并将处理它们。shared_ptr适用于(希望)极少数情况,即您不知道实例需要多长时间。所以你分享所有权,让最后一个所有者负责破坏和释放。

我不完全清楚你在这里真正需要什么样的所有权。你想要某种遍历吗,即子实例是其他子实例的父母(或者你术语中的管理者)?还是您只需要访问成员?那我就不使用指针了,只使用引用。

您正在按值将指针obj传递给getChild(获取副本)。如果将本地obj指针更改为指向不更改主作用域中obj指针的其他对象。主作用域中的obj指针仍然指向垃圾。

由于obj是out参数,因此编写getChild的一种更惯用的方法是使用返回值:

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

你可以这样使用:

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

不,您不应该将shared_ptr用于主作用域中的obj。至少不像现在写的那样。目前,child1child2Manager所有,因为它们由值成员变量所有。除了Manager,您不希望其他人删除它们。

但也许这不是你想要的设计。也许您的意图更像原型模式,其中getChild返回child1child2的克隆,该克隆应该由调用方拥有。

我认为你不应该在堆栈上分配child1和child2,而是应该使用新的运算符在头上分配。第二个修改是在一些不同的函数中获得任何新的内存分配时,将用户指针指向指针。修改后的程序如下:

    #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();
                      }