对象的类型在has-a关系 - C ++ [英] Object's type in a has-a relationship - C++

查看:237
本文介绍了对象的类型在has-a关系 - C ++的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下代码(也可从 C ++ Shell 获得)。



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



当我想在Organization类中定义一个getEmployee(int) 。它应该返回什么类型的员工?如果它返回一个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组织{
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);

组织org1;
org1.addEmployee(* pFt1);
FullTimeEmployee * pFt2 =(FullTimeEmployee *)org1.getEmployee(5);
cout<< pFt2-> getId()<< endl;
cout<< pFt2-> getGrade()<< endl;
}


是的。



长的答案:也许, $ b

多态性的主要观点是提出这样的基本逻辑:这是什么类型的员工?这是一个兼职员工?为兼职员工做正确的事情是自动进行,而不是手动进程。继承和虚拟函数是自动执行此操作的一种方法。



这可以成为一个非常有用的概念,以减少维护开销,因为你现在可以添加新的员工类型到你的心内容,而不是不断地编写代码检查你在哪里工作的什么类型的员工。你最终会转向通常是一个不可扩展的解决方案,这将需要你手动扩展你可能在你的代码库中潜在的许多地方支持的类型范围内的东西,你可以扩展到非侵入性的一面,而不用触摸你写的任何东西到目前为止。



抽象也有助于您识别所有类型共享的公共分母接口。抽象地思考一般是关注什么事情应该做什么,不是具体的细节是什么。专注于帮助你专注于应该做什么,并将给你的代码库更大程度的灵活性,以演变变化,因为你可以换掉什么东西,而不打破代码,指定他们应该做什么。



我喜欢使用的一个基本比喻是,如果你写了一堆船代码告诉机器人走到到各个地方,用轮子交换机器人的腿打破所有的代码,你将不得不重写它所有。相反,如果代码更抽象,并告诉机器人只是到各个地方,你可以用翘曲驱动器,jetpacks和分子传送器和所有的代码来交换机器人的腿写将继续工作。软件工程的很多重点在于创建可维护的代码库的科学(也许是艺术)。其中很大一部分是使变更的成本更便宜,因此,您希望有更多的这些解决方案可以自动适应您的更改,而更少的那些您必须手动适应您的更改。



那么你的问题:


首先,是否可以安全地返回一个指针?我假设它的
地址是永久的,因为它将在empList中发送该元素的地址
。是吗?


指针在这里完全正确。使用原始指针的大多数危险与内存管理(所有权)相关联,而不是访问。如果你的组织类是处理内存管理责任的类,并且 Employee * 只是使用一个句柄来访问特定的员工,这是完全正常的。使用 shared_ptr 这样的东西将是过度的,除非你需要那种共享所有权。


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


你通常想要一个成绩的概念适用于所有员工,函数或非虚函数,并在您的抽象 Employee 基类中实际存储该成绩。请记住,多态性是关于设计一个公共接口,所有这样的员工都会有共同的,无论他们是兼职还是全职,无论他们的特殊性等。



现在关于您的代码:

  std :: list< Employee> empList; 

这是一个问题。你不希望按价值存储 Employee ,否则它会去掉特定员工子类所需的数据。它需要 Employee * 或更好,像 shared_ptr 向量< unique_ptr< Employee> ,您为其指定特定员工子类型对象的内存地址,例如 new PartTimeEmployee



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

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

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


Consider the following code (also available at C++ Shell).

Basically, we have an Employee base class with FullTime and PartTime subclasses. The organization class HAS a list of Employees (who could be Fulltime or PartTime).

The problem arises when I want to define a getEmployee(int) method in the Organization class. What type of Employee should it return? If it returns a Employee*:

  1. first of all, is it safe to return a pointer? I assume that its address is permanent, because it will send the address of that element in the empList. right?
  2. When I cast Employee* to a FullTimeEmployee*, obviuosly I lose the grade field. How can I resolve this?

According to the answer Here, if I need to know "what class an object is, that usually indicates a design flaw...". Is it also the case 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;
    }

解决方案

Short answer: yes.

Long answer: maybe, but probably yes in your case.

The primary point of polymorphism is to make that kind of basic logic of asking, "What type of employee is this? Oh, it's a part-time employee? Do the right thing for part-time employees" an automatic, rather than manual process. Inheritance and virtual functions are one means of doing this automatically.

This can become a very useful concept to reduce maintenance overhead, as you can now add new employee types to your heart's content without constantly writing code checking what type of employee you are working with everywhere. You end up turning what would normally be a inextensible solution that would require you to manually extend the range of types you support in potentially many places intrusively in your codebase into something you can extend to the side non-intrusively without touching anything you've written so far.

The abstraction also helps you identify a common denominator interface that all types share. To think abstractly is generally focusing on what things should all do, not specific details of what they are. Focusing on that helps you concentrate on what things should be doing, and will give your codebase a greater degree of flexibility towards evolving changes as you can swap out what things are without breaking the code specifying what they should do.

A basic analogy I like to use is that if you wrote a boatload of code telling a robot to walk to various places, swapping the legs of that robot with wheels would break all of that code and you would have to rewrite it all. If, instead, the code is more abstract and telling the robot to merely go to various places, you can swap the robot's legs with a warp drive, jetpacks, and molecular teleporters and all of the code you painstakingly wrote would continue to work. A lot of the focus of software engineering is in the science (and perhaps art) of creating maintainable codebases. A large part of that is making the cost of changes cheaper, and for that you want more of these solutions which automatically adapt with your changes, and fewer of those which you have to manually adapt against your changes.

So on to your question:

first of all, is it safe to return a pointer? I assume that its address is permanent, because it will send the address of that element in the empList. right?

Pointers are perfectly fine here. Most of the dangers of using raw pointers are associated with memory management (ownership), not access. If your organization class is the one dealing with the memory management responsibility and Employee* is just used a handle to access a specific employee, it's perfectly fine. Using things like shared_ptr here would be overkill unless you need that kind of shared ownership.

When I cast Employee* to a FullTimeEmployee*, obviuosly I lose the grade field. How can I resolve this?

You generally either want the notion of a 'grade' to be applicable to all employees with either a virtual function or a non-virtual function and actual storage of the grade in your abstract Employee base class. Remember that polymorphism is about designing a public interface that all such employees would have in common regardless of whether they're part-time or full-time, regardless of their specific specialty, etc.

Now about your code:

std::list<Employee> empList;

This is a problem. You don't want to store Employee like this by value or else it's going to slice away the data required for a specific employee subclass. It needs to be Employee* or better, something like a shared_ptr or vector<unique_ptr<Employee>> to which you assign the memory address for specific employee subtype objects like new PartTimeEmployee.

We also need a virtual destructor in Employee so that when you or a smart pointer try to call delete through Employee*, it triggers the correct destruction logic for the specific type of employee we're pointing to like so:

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

If I try to explain why informally, it's because the system doesn't know what Employee* is pointing to. So if you try to destroy that, it won't know what to destroy specifically unless there's a virtual destructor which tells it that the destructor logic can be different for different employee subtypes.

这篇关于对象的类型在has-a关系 - C ++的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆