17.8 – Скрытие унаследованной функциональности

Добавлено 31 июля 2021 в 21:57

Изменение уровня доступа унаследованного члена

C++ дает нам возможность изменять в производном классе спецификатор доступа унаследованного члена. Это делается с помощью объявления using для идентификации члена базового класса (ограниченного по области видимости), доступ к которому должен быть изменен в производном классе в соответствии с новым спецификатором доступа.

Например, рассмотрим следующий класс Base:

#include <iostream>
 
class Base
{
private:
    int m_value;
 
public:
    Base(int value)
        : m_value(value)
    {
    }
 
protected:
    void printValue() { std::cout << m_value; }
};

Поскольку Base::printValue() объявлена как защищенная, она может вызываться только классом Base или его производными классами. Внешний код не может получить к ней доступ.

Давайте определим класс Derived, который изменяет спецификатор доступа printValue() на public:

class Derived: public Base
{
public:
    Derived(int value)
        : Base(value)
    {
    }
 
    // Base::printValue была унаследована как защищенная,
    // поэтому к ней нет открытого доступа.
    // Но мы меняем доступ к ней на открытый через объявление using
    using Base::printValue; // обратите внимание: здесь нет скобок
};

Это означает, что теперь следующий код будет работать:

int main()
{
    Derived derived(7);
 
    // printValue открытая в Derived, поэтому это нормально
    derived.printValue(); // печатает 7
    return 0;
}

Вы можете изменить спецификаторы доступа только тех базовых членов, к которым производный класс обычно имеет доступ. Следовательно, вы никогда не сможете изменить спецификатор доступа базового члена с private на protected или public, поскольку производные классы не имеют доступа к закрытым членам базового класса.

Скрытие функциональности

В C++ невозможно удалить или ограничить функциональность базового класса, кроме как путем изменения исходного кода. Однако в производном классе можно скрыть функционал, существующий в базовом классе, чтобы к нему нельзя было получить доступ через этот производный класс. Это можно сделать, просто изменив соответствующий спецификатор доступа.

Например, мы можем сделать открытый член закрытым:

#include <iostream>
class Base
{
public:
    int m_value;
};
 
class Derived : public Base
{
private:
    using Base::m_value;
 
public:
    Derived(int value)
    // Мы не можем инициализировать m_value, так как это член Base
    // (его должен инициализировать Base)
    {
        // Но мы можем присвоить ему значение
        m_value = value;
    }
};
 
int main()
{
    Derived derived(7);
 
    // Следующая строка не будет работать, потому
    // что m_value был переопределен как закрытый
    std::cout << derived.m_value;
 
    return 0;
}

Обратите внимание, что это позволило нам взять плохо спроектированный базовый класс и инкапсулировать его данные в нашем производном классе. В качестве альтернативы, вместо того, чтобы наследовать члены Base открыто и делать m_value закрытым, переопределив его спецификатор доступа, мы могли бы наследовать Base закрытым образом, что в первую очередь привело бы к закрытому унаследованию всех членов Base.

Вы также можете в производном классе пометить функции-члены как удаленные, что гарантирует, что они вообще не могут быть вызваны через объект производного класса:

#include <iostream>
class Base
{
private:
    int m_value;
 
public:
    Base(int value)
        : m_value(value)
    {
    }
 
    int getValue() { return m_value; }
};
 
class Derived : public Base
{
public:
    Derived(int value)
        : Base(value)
    {
    }
 
    int getValue() = delete; // помечаем эту функцию как недоступную
};
 
int main()
{
    Derived derived(7);
 
    // Следующий код не работает, потому что getValue() удалена!
    std::cout << derived.getValue();
 
    return 0;
}

В приведенном выше примере мы отметили функцию getValue() как удаленную. Это означает, что компилятор будет жаловаться, когда мы попытаемся вызвать версию этой функции для класса Derived. Обратите внимание, что версия getValue() для класса Base всё еще доступна. Это означает, что объект Derived всё еще может получить доступ к getValue(), предварительно преобразовав объект Derived в Base:

int main()
{
    Derived derived(7);
 
    // Мы все еще можем получить доступ к функции, удаленной
    // в классе Derived, через класс Base
    std::cout << static_cast<Base>(derived).getValue();
 
    return 0;
}

Теги

C++ / CppLearnCppusingДля начинающихКласс (программирование)НаследованиеОбучениеОбъектно-ориентированное программирование (ООП)Программирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.