Каково правильное решение для доступа к переменным подкласса? - PullRequest
2 голосов
/ 25 апреля 2020

Как я могу получить доступ к sideA и height членам класса Triangle и как я могу получить доступ к sideA класса Square, оба они получены из класса 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;
}

Ответы [ 4 ]

3 голосов
/ 25 апреля 2020

Вы пытаетесь получить доступ к информации, которая недоступна через интерфейс, который вы определили, 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 голосов
/ 25 апреля 2020

Поскольку ваш базовый класс имеет функцию 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() { }
};

Для дальнейшего использования обсуждение необходимости виртуального деструктора см. здесь: Когда использовать виртуальные деструкторы? .


РЕДАКТИРОВАТЬ: В вашем конкретном c случае, ответ , данный rustyx , действительно является «правильным» подходом; однако полезно понять / оценить использование опции dynamic_cast, поскольку это может быть решение only , если вы производите классы от стороннего базового класса, который вы не можете изменить, и таким образом, нельзя добавить к нему эквивалент функции getHeight().
2 голосов
/ 25 апреля 2020

A Shape не имеет height. Вы используете треугольник полиморфно. Это означает, что у вас есть Shape* и вы можете использовать только интерфейс Shape, независимо от того, каков фактический тип объекта. Если вы хотите Triangle, используйте Triangle, а не Shape. Если вы все еще хотите полиморфно использовать Triangle и Rectangle, то вы должны поместить общий интерфейс в базовый класс. В вашем случае оба имеют 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, но такие броски часто являются признаком плохого дизайна. Вы должны стремиться писать классы так, чтобы вам не требовалось приведение.

2 голосов
/ 25 апреля 2020

Вы можете объявить переменную a 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
};

Поскольку у вас уже есть абстрактный класс, почему бы не использовать его для доступа к данным в производных классах:

Вот как Я бы сделал это :

#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
}

Взгляните на умные указатели .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...