Наследование классов. Режим protected

Практический курс по ООП C++: https://stepik.org/a/205781

Смотреть материал на YouTube | RuTube

Итак, мы с вами подошли, наверное, к самому главному моменту в ООП – наследованию классов. Наследование – это то, что превращает программу из простого набора объектов в настоящую концепцию программирования, то, что выводит нас на совершенно новый уровень проектирования и написания программного кода. И совсем скоро, вы своими глазами сможете в этом убедиться.

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

Тогда, например, класс для прямых линий (Line) можно объявить следующим образом:

class Line {
    int x0{0}, y0{0}, x1{0}, y1{0};
    double length{0.0};
 
public:
    void set_coords(int x0, int y0, int x1, int y1)
    {
        this->x0 = x0; this->y0 = y0;
        this->x1 = x1; this->y1 = y1;
    }
};

Здесь дополнительно прописана переменная length, содержащая длину линии, и эта переменная характерна только для класса Line.

По аналогии можно было бы объявить и остальные классы, но я пока не стану этого делать. Мы отметим следующее. Все фигуры имеют общие элементы в описании – это координаты x0, y0, x1, y1. Было бы логично выделить их в общий класс. Давайте это сделаем. Определим класс для произвольных геометрических фигур с именем GeomBase:

class GeomBase {
private:
    int x0{0}, y0{0}, x1{0}, y1{0};
};

И класс Line унаследуем от этого класса:

class Line : public GeomBase {
private:
    double length{0.0};
 
public:
    void set_coords(int x0, int y0, int x1, int y1)
    {
        this->x0 = x0; this->y0 = y0;
        this->x1 = x1; this->y1 = y1;
    }
};

Обратите внимание, как записывается наследование классов в языке C++. Ставится двоеточие, затем, режим наследование (в примере public) и имя базового класса GeomBase. О режимах наследования речь еще впереди.

Соответственно, класс, от которого выполняется наследование, называют базовым, родительским, а класс наследник – дочерним, производным.

Однако при такой организации у нас возникли некоторые проблемы. В методе set_coords стали недоступны переменные x0, y0, x1, y1. С чем это связано? Смотрите, эти переменные расположены в секции private базового класса GeomBase. А эта секция ограничивает доступ к ее элементам в пределах текущего класса, и не важно, что класс Line является дочерним для класса GeomBase. Мы можем работать с координатами только в классе GeomBase. Например, если перенести метод set_coords в базовый класс, то никаких ошибок не будет:

class GeomBase {
private:
    int x0{0}, y0{0}, x1{0}, y1{0};
public:
    void set_coords(int x0, int y0, int x1, int y1)
    {
        this->x0 = x0; this->y0 = y0;
        this->x1 = x1; this->y1 = y1;
    }
};

При этом в дочернем классе Line совершенно спокойно можно вызывать публичный метод set_coords и обращаться к любым другим публичным полям базового класса. Давайте для примера добавим в базовый класс еще один метод для получения текущих координат:

    void get_coords(int& x0, int& y0, int& x1, int& y1)
    {
        x0 = this->x0; y0 = this->y0;
        x1 = this->x1; y1 = this->y1;
    }

А в дочернем классе определим метод draw для отображения объекта линии:

class Line : public GeomBase {
private:
    double length{0.0};
public:
    void draw()
    {
        int x0, y0, x1, y1;
        get_coords(x0, y0, x1, y1);
        printf("Line: %d, %d, %d, %d\n", x0, y0, x1, y1);
    }
};

После этого в функции main можно создать объект класса Line и вызвать для него соответствующие методы:

int main()
{
    Line ln;
    
    ln.set_coords(1, 2, 10, 20);
    ln.draw();
 
    return 0;
}

Режим доступа protected

Однако обращение к переменным-координатам базового класса GeomBase в методе draw дочернего класса Line через публичные методы – не лучшее решение. Было бы хорошо эти координаты сделать закрытыми от доступа извне, но сделать доступными во всех производных от него классах. И такой режим в ООП существует. Он называется protected. Если мы в базовом классе вместо режима private укажем protected:

class GeomBase {
protected:
    int x0{0}, y0{0}, x1{0}, y1{0};
public:
...
};

то к переменным x0, y0, x1, y1 можно обращаться напрямую из всех его дочерних классов. Соответственно, метод draw класса Line значительно упрощается до одной строчки:

class Line : public GeomBase {
private:
    double length{0.0};
public:
    void draw()
    {
        printf("Line: %d, %d, %d, %d\n", x0, y0, x1, y1);
    }
};

Видите, как это бывает удобно. При этом вне классов получить доступ к этим переменным по-прежнему запрещено. Следующая команда приведет к ошибке:

Line ln;
ln.x0 = 10; // ошибка

То есть, в классах можно прописывать три разных режима:

  • private – доступ в пределах текущего класса;
  • protected – доступ в пределах текущего и всех дочерних от него классах;
  • public – общий доступ (внутри и за пределами класса).

Надо отметить, что помещать переменные в секцию protected следует с особой осторожностью, т.к. дочерние классы получают к ним прямой доступ с произвольной логикой их обработки. Поэтому protected-переменные следует воспринимать, как особый вид публичных переменных и при проектировании логики класса (где они объявлены) относиться к ним как к публичным.

Практический курс по ООП C++: https://stepik.org/a/205781

Видео по теме