关系中的对象类型 - C++

Object's type in a has-a relationship - C++

本文关键字:C++ 类型 对象 关系      更新时间:2023-10-16

考虑以下代码(也可在c++ Shell中获得):

基本上,我们有一个Employee基类与FullTime和PartTime子类。组织类有一个Employees列表(可以是Fulltime或PartTime)。

当我想在Organization类中定义getEmployee(int)方法时,问题就出现了。它应该返回什么类型的Employee ?如果返回Employee*:

  1. 首先,返回指针是安全的吗?我假设它的地址是永久的,因为它将在empList中发送该元素的地址。对吧?
  2. 当我将Employee*转换为FullTimeEmployee*时,显然我失去了等级字段。我该如何解决这个问题?

根据这里的答案,如果我需要知道"对象是什么类,这通常表明设计缺陷…"。这里也是这样吗?

 #include <iostream>
    #include <string>
    #include <list>
    #include <algorithm>
    using namespace std;
    class Employee {
     public:
      int getId() { return id;}
       void setId( int id) {this->id = id;}
     protected:
      int id;
    };
    class PartTimeEmployee : public Employee {
    };
    class FullTimeEmployee : public Employee {
     public:
      int getGrade() {return grade;}
      void setGrade(int grade) {this->grade = grade;}
     private:
      int grade;
    };
    class Organization {
     public:    
      void addEmployee(Employee& e) { empList.push_back(e); }
      Employee* getEmployee(int id) { 
          for (std::list<Employee>::iterator it=empList.begin(); it!=empList.end(); ++it) {
              if(it->getId() == id) {return &(*it);}
          }
          return NULL; 
      }
     private:
      std::list<Employee> empList;
    };
    int main()
    {
      Employee e1;
      e1.setId(5);
      FullTimeEmployee *pFt1 = (FullTimeEmployee*) &e1; 
      pFt1->setGrade(1);                
      Organization org1;
      org1.addEmployee(*pFt1);
      FullTimeEmployee *pFt2 = (FullTimeEmployee*) org1.getEmployee(5);
      cout << pFt2->getId() << endl;
      cout << pFt2->getGrade() << endl;
    }
首先,返回一个指针安全吗?

是的。该对象在列表中,因此指针和对它的引用在删除它之前一直有效。

当我将Employee*转换为FullTimeEmployee*时,显然我失去了等级字段。

你已经失去了它之前-当你转换到Employee把它放在列表中。如果希望存储不同类型的对象(使用公共基类),则必须存储指针,并调整addEmployee以允许存储子类型。然后,您可以在检索指针后使用RTTI来区分类型。

记住管理对象的生命周期(可能使用智能指针),并且可能需要给基类一个虚析构函数(为了正确删除和启用RTTI)。

如果我需要知道"对象是什么类,这通常表明设计缺陷…"。这里也是这样吗?

这是一个意见问题,但我认为是。在我看来,添加一个属性(或者可能是一个特殊的"grade"值)来表示就业状态似乎比在继承及其所有相关的复杂问题上折腾更简单。

简短的回答:是的。

长答案:也许,但对你来说可能是。

多态的主要目的是使问题的基本逻辑,"这是什么类型的员工?"哦,是兼职员工吗?为兼职员工做正确的事"一个自动的,而不是手动的过程。继承和虚函数是自动完成此任务的一种方法。

这可以成为一个非常有用的概念,以减少维护开销,因为您现在可以向您的核心内容添加新的员工类型,而无需不断编写代码检查您到处使用的员工类型。你最终会把通常是一个不可扩展的解决方案,这将需要你手动扩展你支持的类型的范围,在潜在的许多地方侵入你的代码库变成一些你可以扩展到非侵入性的东西,而不触及任何你已经写了到目前为止。

抽象还帮助您识别所有类型共享的公分母接口。抽象思考通常是关注事物应该做什么,而不是它们是什么的具体细节。专注于此可以帮助您专注于应该做的事情,并将为您的代码库提供更大程度的灵活性,以适应不断变化的变化,因为您可以在不破坏指定它们应该做什么的代码的情况下交换事物。

我喜欢用的一个基本类比是,如果你写了一大堆代码告诉机器人到不同的地方,把机器人的腿换成轮子会破坏所有的代码,你必须重写所有的代码。相反,如果代码更抽象,告诉机器人仅仅是到不同的地方,你可以用曲速引擎、喷气背包和分子传送器替换机器人的腿,你辛苦编写的所有代码将继续工作。软件工程的许多重点是创建可维护的代码库的科学(也许是艺术)。其中很大一部分是使更改的成本更便宜,因此您需要更多的这些自动适应您的更改的解决方案,以及更少的您必须手动适应您的更改的解决方案。

继续你的问题:

首先,返回一个指针安全吗?我想这是Address是永久的,因为它将发送该元素的地址在列表中。对吧?

指针在这里完全没问题。使用原始指针的大多数危险都与内存管理(所有权)有关,而与访问无关。如果您的组织类是处理内存管理责任的类,并且Employee*只是使用句柄来访问特定的员工,则完全可以。在这里使用shared_ptr这样的东西是多余的,除非你需要那种共享所有权。

当我将Employee*转换为FullTimeEmployee*时,显然我失去了级字段。我该如何解决这个问题?

您通常希望'grade'的概念适用于所有具有虚函数或非虚函数的员工,并且在抽象Employee基类中实际存储了grade。请记住,多态性是关于设计一个公共接口,所有这样的员工都将拥有这个公共接口,而不管他们是兼职还是全职,也不管他们的特定专业如何,等等。

现在关于你的代码:

std::list<Employee> empList;

这是个问题。您不希望像这样按值存储Employee,否则它将剥离特定员工子类所需的数据。它需要是Employee*或更好的,像shared_ptrvector<unique_ptr<Employee>>,你为特定的员工子类型对象(如new PartTimeEmployee)分配内存地址。

我们还需要在Employee中使用虚拟析构函数,以便当您或智能指针尝试通过Employee*调用delete时,它会为我们指向的特定类型的员工触发正确的析构逻辑,如下所示:

   class Employee {
   public:
       virtual ~Employee() {}
       ...
   };

如果我试图非正式地解释为什么,那是因为系统不知道Employee*指向什么。所以如果你试图销毁它,它将不知道具体销毁什么除非有一个虚拟析构函数告诉它析构函数逻辑对于不同的雇员子类型是不同的。

您是否对Employee(基类或派生类)对象在内存中的位置有一些详细的要求?