【问题标题】:What is the right solution to access subclass variables?访问子类变量的正确解决方案是什么?
【发布时间】:2020-08-09 02:15:33
【问题描述】:

如何访问Triangle 类的sideAheight 成员,以及如何访问Square 类的sideA,这些都是从Shape 类派生的?

实现它的正确方法是什么?

Shapes.h:

class Shape
{
public:
    virtual double getArea() = 0;
};

class Triangle : public Shape 
{
public:
    double sideA = 3;
    double height = 2;
    double getArea() {
        return 0.5 * sideA * height;
    }
};

class Square : public Shape 
{
public:
    double sideA = 4;

    double getArea() {
        return sideA * sideA;
    }
};

Main.cpp:

int main()
{
    Shape* sh = new Triangle();
    std::cout << sh->getArea() << std::endl;
    std::cout << sh->??? //get the height of triangle
    delete sh;
}

【问题讨论】:

    标签: c++ class inheritance polymorphism


    【解决方案1】:

    您正在尝试通过您定义的接口访问不可用的信息,class Shape 只允许访问 区域

    要获得高度,正确的方法是扩展接口以提供该信息。

    class Shape
    {
    public:
        virtual double getArea() = 0;
        virtual double getHeight() = 0;
    };
    
    class Triangle : public Shape 
    {
    public:
        double sideA = 3;
        double height = 2;
        double getArea() {
            return 0.5 * sideA * height;
        }
        double getHeight() {
            return height;
        }
    };
    
    class Square : public Shape 
    {
    public:
        double sideA = 4;
    
        double getArea() {
            return sideA * sideA;
        }
        double getHeight() {
            return sideA;
        }
    };
    

    【讨论】:

      【解决方案2】:

      Shape 没有 height。您正在多态地使用三角形。这意味着你有一个Shape* 并且只能使用Shape 的接口,无论对象的实际类型是什么。如果您想要Triangle,请使用Triangle 而不是Shape。如果您仍想多态地使用TriangleRectangle,那么您应该将公共接口放入基类中。在你的情况下,两者都有一个sideA,所以你可以这样做:

      struct Shape {
          double sideA = 3;
          virtual double getArea() = 0;
          virtual ~Shape(){}   
      };
      
      struct Triangle : public Shape {
          double height = 2;
          double getArea() {
              return 0.5 * sideA * height;
          }
      };
      
      struct Square : public Shape {
          double getArea() {
              return sideA * sideA;
          }
      };
      
      int main() {
          Shape* sh = new Triangle();
          std::cout << sh->sideA;
          delete sh;
      }
      

      PS:以上内容并非全部真相。如果你有一个Shape*,并且你知道它是一个Triangle*,那么你可以使用dynamic_cast,但是这样做通常是设计不佳的标志。您应该努力编写不需要演员表的类。

      【讨论】:

      • 你的“PS”是一个很好的观点在这种情况下,你的回答非常好。但是,在某些情况下需要dynamic_cast(请参阅我的回答中的“编辑”)。
      • @ArianMole 谢谢,我认为你解释如何使用 dynamic_cast 的回答是一个重要的贡献,我只是认为对于多态性的新手来说,提到不全部(PS 真的以 PS 的形式出现)。但是,尽管我总体上同意您的回答,但我不会在“编辑”中购买您的论点,而不是强制转换,而是可以提供具有所需接口的基类并使该基类从第三个继承党课
      • 你的论据很好。然而,我想到的一个“真实世界”案例是从 MFC CDocument 派生类时。有一些框架函数会返回一个原生的CDocument* 指针,并且您经常需要测试您指向的文档类型。如果应用程序可以支持您未定义的文档类型,那么添加额外的“层”将无济于事。
      • @AdrianMole 啊好吧,我知道我的观点有漏洞,但我没有找到。感谢您的澄清
      • @AdrianMole 是的,这个世界不是由好的设计组成的,我们必须忍受它;)
      【解决方案3】:

      因为您的基类有一个virtual 函数1,您可以使用dynamic_cast 转换来检查指向它的指针是否实际上是指向其派生类之一的指针。这将返回nullptr,如果它属于“测试”类,或者是一个指向派生类的可用指针,如果它是:

      int main()
      {
          Shape* sh = new Triangle();
          std::cout << sh->getArea() << std::endl;
          if (dynamic_cast<Square*>(sh) != nullptr) { // Check for a valid Square pointer
              Square* sq = dynamic_cast<Square*>(sh);
              std::cout << sq->sideA << std::endl;
          }
          else if (dynamic_cast<Triangle*>(sh) != nullptr) { // Check for a valid Trianlge pointer
              Triangle* tr = dynamic_cast<Triangle*>(sh);
              std::cout << tr->height << std::endl;
          }
          else {
              std::cout << "Unspecified shape type: height unknown!" << std::endl;
          }
          delete sh;
          return 0;
      

      1请注意,因为你的Shape类中有一个虚函数,你还应该给它一个虚析构函数:

      class Shape {
      public:
          virtual double getArea() = 0;
          virtual ~Shape() { }
      };
      

      有关虚拟析构函数需求的进一步讨论,请参见此处:When to use virtual destructors?


      编辑:在您的具体情况下,answer given by rustyx 确实是“正确”的方法;但是,了解/欣赏dynamic_cast 选项的使用很有用,因为如果您从无法修改的第三方基类派生类,这可能是唯一解决方案,因此无法向其添加等效的 getHeight() 函数。

      【讨论】:

        【解决方案4】:

        您可以将变量声明为Triangle* 而不是Shape*,这样您就可以访问派生类和基类的方法和变量:

        int main()
        {
            Triangle* sh = new Triangle();
            Square* sh2 = new Square();
            std::cout << sh->getArea() << std::endl; //3
            std::cout << sh2->getArea() << std::endl; //16
            std::cout << sh->sideA << std::endl; //3
            std::cout << sh2->sideA << std::endl; //4
            delete sh;
        }
        

        要安全地使用delete sh,你应该有一个虚拟析构函数

        class Shape
        {
        public:
            virtual double getArea() = 0;
            virtual ~Shape(){} //virtual destructor
        };
        

        既然你已经有了一个抽象类,为什么不使用它来访问派生类中的数据:

        Here is how I would do it:

        #include <iostream>
        #include <memory>
        
        class Shape
        {  
        private:
            double sideA; //shared members can be defined in base class, assuming all
                          //derived classes will have sideA member
        protected:    
            Shape(double sideA) : sideA(sideA) {}//for initialization of sideA in derived classes
        public:    
            Shape() = default;
            virtual double getArea() = 0;
            double getSideA() { //shared logic
                return sideA;
            }
            virtual ~Shape(){} //virtual destructor
        };
        
        class Triangle : public Shape 
        {
        private:
            double height = 2; //specific property
        public:    
            Triangle() : Shape(3) {} //intialize sizeA
            double getHeight(){ //specific method, must instanciate Triangle to access
                                //for collections it's best to use interface method like getArea()
                 return height;
            }
            double getArea() override {
                return 0.5 * getSideA() * height;
            }   
        };
        
        class Square : public Shape 
        {
        public:
            Square() : Shape(4) {} //intialize sizeA
            double getArea() override {
                return getSideA() * getSideA();
            }
        };
        
        
        int main()
        {
            std::unique_ptr<Shape> sh(new Triangle); //smart pointer
            std::unique_ptr<Shape> sh2(new Square);  //smart pointer
        
            std::cout << sh->getArea() << std::endl; //3
            std::cout << sh2->getArea() << std::endl; //16
            std::cout << sh->getSideA() << std::endl; //3
            std::cout << sh2->getSideA() << std::endl; //4
            //delete sh; //no need, smart pointer
        }
        

        看看smart pointers

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-10-10
          • 2014-04-08
          • 1970-01-01
          • 2021-11-04
          • 1970-01-01
          • 2020-08-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多