请考虑以下代码(也可在C++ Shell处获得)。
基本上,我们有一个带有FullTime和PartTime子类的Employee基类。组织类有一个Employees列表(可以是Fulltime或PartTime)。
当我想在Organization类中定义getEmployee(int)方法时出现问题。应该返回什么类型的员工?如果它返回一个Employee *:
根据答案Here,如果我需要知道“对象是什么类,通常表明设计缺陷......”。这也是这种情况吗?
#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;
}
答案 0 :(得分:3)
首先,返回指针是否安全?
是。该对象位于列表中,因此指针和对它的引用在被删除之前一直有效。
当我将
Employee*
投射到FullTimeEmployee*
时,显然我失去了等级字段。
在您转换为Employee
并将其放入列表之前,您已经丢失了它。如果要存储不同类型的对象(使用公共基类),则必须存储指针,并调整addEmployee
以允许存储子类型。然后,您可以使用RTTI在检索指针后区分类型。
请记住管理对象的生命周期(可能使用智能指针),并且您可能需要为基类提供虚拟析构函数(正确删除和启用RTTI)。
如果我需要知道“对象是什么类,通常表明设计缺陷......”。这也是这种情况吗?
这是一个意见问题,但我会说是的。添加一个属性(或者可能是“等级”的特殊值)来表示就业状况,而不是继承遗产及其相关的并发症,这对我来说似乎更简单。
答案 1 :(得分:3)
简短回答:是。
答案很长:也许,但在你的情况下可能是肯定的。
多态性的主要观点是提出这样一种基本的问题逻辑,&#34;这是什么类型的员工?哦,这是一名兼职员工?为兼职员工做正确的事情&#34; 是一个自动而非手动的过程。继承和虚函数是自动执行此操作的一种方法。
这可以成为一个非常有用的概念来减少维护开销,因为您现在可以在您的内容中添加新的员工类型,而无需经常编写代码来检查您在哪里工作的员工类型。你最终会转向通常不可扩展的解决方案,这需要你手动扩展你支持的类型范围,在你的代码库中可能会侵入很多地方,你可以非侵入地延伸到一边而不会触及你的任何东西。到目前为止写的。
抽象还可以帮助您识别所有类型共享的公分母界面。抽象地思考通常关注的是事情应该做什么,而不是关于它们是什么的具体细节。专注于这一点可以帮助您专注于应该做的事情,并且会为您的代码库提供更大程度的灵活性来适应不断变化的变化,因为您可以在不破坏代码的情况下更换出来的内容,并指明应该做什么。
我喜欢使用的一个基本类比是,如果你写了一大堆代码告诉机器人走到各个地方,用轮子交换机器人的腿就会破坏所有的代码和你必须重写它。相反,如果代码更抽象并且告诉机器人仅仅去到各个地方,你可以将机器人的腿换成经线驱动器,喷气背包和分子传送器以及所有的你辛苦写的代码将继续有效。软件工程的许多重点在于创建可维护代码库的科学(也许是艺术)。其中很大一部分是使更改成本更便宜,为此您需要更多这些解决方案,这些解决方案会根据您的更改自动调整,而您需要手动调整更改的更少。
关于你的问题:
首先,返回指针是否安全?我认为它 地址是永久性的,因为它将发送该元素的地址 在empList中。正确?
指针在这里非常好。使用原始指针的大多数危险都与内存管理(所有权)有关,而与访问无关。如果您的组织类是处理内存管理职责的组织类,而Employee*
只是用来访问特定员工的句柄,那就完全没问题了。除非您需要这种共享所有权,否则在shared_ptr
这样使用此类内容会有点过分。
当我将Employee *转换为FullTimeEmployee *时,显然我失去了 等级字段。我该如何解决这个问题?
你通常要么想要等级&#39;适用于具有虚拟功能或非虚拟功能的所有员工以及抽象Employee
基类中等级的实际存储。请记住,多态性是关于设计一个所有这些员工都有共同点的公共界面,无论他们是兼职还是全职,无论他们的专业是什么等等。
现在关于你的代码:
std::list<Employee> empList;
这是一个问题。您不希望按价值存储Employee
,或者它会切断特定员工子类所需的数据。它必须是Employee*
或更好,例如shared_ptr
或vector<unique_ptr<Employee>>
,您可以为new PartTimeEmployee
指定特定员工子类型对象的内存地址。
我们还需要Employee
中的虚拟析构函数,以便当您或智能指针尝试通过delete
调用Employee*
时,它会触发特定类型员工的正确销毁逻辑我们指的是这样:
class Employee {
public:
virtual ~Employee() {}
...
};
如果我试图解释为什么非正式,那是因为系统不知道Employee *指向的是什么。因此,如果你试图破坏它,它就不会知道要具体销毁什么,除非有一个虚拟析构函数告诉它析构函数逻辑对于不同的员工子类型可能是不同的。
答案 2 :(得分:1)
您是否对内存中的Employee(基础或派生)对象的位置有一些详细要求?