有了父子类层次结构,如何在单个 "Parent" 类型变量中保存各种实际子级?



假设我有4个类:

  1. 父母
  2. 儿童1
  3. 儿童2
  4. 儿童3

所有子级都是类父级的后代。

在我的玩具程序中,我必须为每个孩子创建一个单独的变量,然后处理需要处理的内容。 但是我希望有一个父类型的变量,可以转换为子变量。

这是我当前的解决方案:

int main(int argc, char* argv[]) {
    Child1 c1 = Child1();
    Child2 c2 = Child2();
    Child3 c3 = Child3();
    switch(arg[1]) {
         case CHILD1:
             c1.start();
             break;
         case CHILD2:
             c2.start();
             break;
         case CHILD3:
             c3.start();
             break;
    }
    return 0;
}

以下是我希望获得的解决方案类型:

int main(int argc, char* argv[]) {
    Parent p = Parent();
    switch(arg[1]) {
         case CHILD1:
             (Child1)p.start();
             break;
         case CHILD2:
             (Child2)p.start();
             break;
         case CHILD3:
             (Child3)p.start();
             break;
    }
    return 0;
}

我知道上面的代码不正确。 但我认为它正确地传达了我想要表达的内容。 我不想浪费内存来创建从未使用过的对象。

想法一:在交换机中创建项目

int main(int argc, char* argv[]) {
    switch(arg[1]) {
         case CHILD1:
             {
                 Child1 p;
                 p.start();
                 break;
             }
         case CHILD2:
             {
                 Child2 p;
                 p.start();
                 break;
             }
         case CHILD3:
             {
                 Child3 p;
                 p.start();
                 break;
             }
    }
    return 0;
}

似乎很多代码都是复制粘贴的,对吗? 右。 如果Parent具有虚拟析构函数和虚拟start方法,则可以使用工厂模式,从而最大程度地减少重复。

std::unique_ptr<Parent> child_factory(char*) {
    switch(arg[1]) {
    case CHILD1: return std::make_unique<Child1>();
    case CHILD2: return std::make_unique<Child2>();
    case CHILD3: return std::make_unique<Child3>();
    default: throw std::runtime_error("invalid child type");
    }
}
int main(int argc, char* argv[]) {
    std::unique_ptr<Parent> p = child_factory(arg[1]);
    p->start();
    return 0;
}

如果这些对象不需要很长时间,那么你可以简单地做:

switch(arg[1]) {
     case CHILD1:
     {
         Child1 c1;
         c1.start();
         break;
     }
     case CHILD2:
     {
         Child2 c2;
         c2.start();
         break;
     }
     case CHILD3:
     {
         Child3 c3;
         c3.start();
         break;
     }
}

另一方面,如果您需要根据Parent类型保留某些内容更长时间,则需要查看分配给基类型指针的动态分配对象。在这种情况下,您可能希望start方法virtual。然后你可以做:

Parent* p = NULL;
switch(arg[1]) {
     case CHILD1:
         p = new Child1;
         break;
     case CHILD2:
         p = new Child2;
         break;
     case CHILD3:
         p = new Child3;
         break;
}
p.start();
// more stuff
delete p;

当然,这里还有很大的改进空间,例如处理arg[1]与任何情况都不匹配的情况。此外,最佳做法是使用智能指针进行p以便更可靠地处理释放。

强制转换语法不是(Child)p.start(),但它是((Child)p).start()。前者表示(Child) (p.start()),因此它将强制转换 start 返回的值,而不是 P。

无论如何,根本不需要强制转换,因为它们都派生自"父级"。阅读有关虚拟方法的信息。使用它们,您可以执行以下操作:

Parent p = ...
p.start();  // the virtual method 'start' takes care of calling the right code

但是#1:没有免费的午餐。虚拟方法确保当你在父级上调用"start"时调用子方法,但仍然必须根据arg[1]选择正确的子方法1/子2/子3。

Parent p = chooseTheChild( arg[1] );   // you need to write it..
p.start();  // the virtual method 'start' takes care of calling the right code

但是#2:它不适用于普通父级。虚拟方法适用于指针

Parent* p = chooseTheChild( arg[1] );   // you need to write it..
p->start();  // the virtual method 'start' takes care of calling the right code

现在你可以编写"选择"函数了。又没有免费的午餐,还是一个开关。由于需要指针,因此您必须new孩子:

class Parent
{
    public:
        ~Parent() { }
        virtual void start() = 0;
};
class Child1 : public Parent
{
    public:
        virtual void start() { std::cout << "start1!" << std::endl; }
};
class Child2 : public Parent
{
    public:
        virtual void start() { std::cout << "start2!" << std::endl; }
};
class Child3 : public Parent
{
    public:
        virtual void start() { std::cout << "start3!" << std::endl; }
};
Parent* chooseTheChild( ChildType ttt)
{
  switch(ttt) {
     case CHILD1: return new Child1();
     case CHILD2: return new Child2();
     case CHILD3: return new Child3();
  }
}
.. somewhere ...
Parent* p = chooseTheChild( arg[1] );
p->start();  // the virtual method 'start' takes care of calling the right code
delete p;  // new'ed? delete it when finished!

这段代码是一个草图,并不意味着 100% 正确和干净,但应该给你一些可以玩的东西。例如,显然需要智能指针而不是普通指针。等。

显而易见的解决方案是在需要时创建孩子。如果您愿意,可以使用函数模板来消除重复代码:

#include <iostream>
struct Child1{
  void start() { std::cout << "Start Child1n"; }    
};
struct Child2{
  void start() { std::cout << "Start Child2n"; }    
};
struct Child3{
  void start() { std::cout << "Start Child3n"; }    
};
template<typename Child>
void start() {
  auto child = Child();
  child.start();
}    
int main(int /*argc*/, char* argv[]) {
  auto type = std::string(argv[1]);
  if (type == "CHILD1")
      start<Child1>();
  else if (type == "CHILD2")
      start<Child2>();
  else if (type == "CHILD3")
      start<Child3>();
}

在这种特殊情况下,如果您不想,则不需要virtual方法,但在更复杂的情况下,您可能希望有一个返回指向Parent的(智能)指针的工厂,并在Parent上具有虚拟析构函数和启动方法。

对不起,但这种明确的想法继承是错误的。 父母孩子之间的关系可以想到:child_object是一个parent_object,这意味着child_object也可以用作parent_object,即可以铸造,称为,...作为一个,反之亦然!

Mooing Duck的第二个例子是好东西。

最新更新