17.8 – Скрытие унаследованной функциональности
Изменение уровня доступа унаследованного члена
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;
}