24.8 — Ukrywanie odziedziczonej funkcjonalności

Zmiana poziomu dostępu odziedziczonego elementu

C++ daje nam możliwość zmiany odziedziczonego poziomu dostępu specyfikator dostępu elementu członkowskiego w klasie pochodnej. Odbywa się to za pomocą za pomocą deklaracji w celu zidentyfikowania członka klasy bazowej (o określonym zakresie), którego dostęp w klasie pochodnej został zmieniony, w ramach nowego specyfikatora dostępu.

Rozważmy na przykład następujący Base:

#include <iostream>

class Base
{
private:
    int m_value {};

public:
    Base(int value)
        : m_value { value }
    {
    }

protected:
    void printValue() const { std::cout << m_value; }
};

Ponieważ Base::printValue() został zadeklarowany jako chroniony, może być wywoływany tylko przez Base lub jego klasy pochodne. Publiczność nie może uzyskać do niego dostępu.

Zdefiniujmy klasę pochodną, ​​która zmienia specyfikator dostępu printValue() na publiczny:

class Derived: public Base
{
public:
    Derived(int value)
        : Base { value }
    {
    }

    // Base::printValue was inherited as protected, so the public has no access
    // But we're changing it to public via a using declaration
    using Base::printValue; // note: no parenthesis here
};

Oznacza to, że ten kod będzie teraz działać:

int main()
{
    Derived derived { 7 };

    // printValue is public in Derived, so this is okay
    derived.printValue(); // prints 7
    return 0;
}

Możesz zmieniać specyfikatory dostępu tylko elementów podstawowych, do których normalnie miałaby dostęp klasa pochodna. Dlatego nigdy nie można zmienić specyfikatora dostępu elementu podstawowego z prywatnego na chroniony lub publiczny, ponieważ klasy pochodne nie mają dostępu do prywatnych elementów klasy bazowej.

Ukrywanie funkcjonalności

W C++ nie można usunąć ani ograniczyć funkcjonalności klasy bazowej inaczej niż poprzez modyfikację kodu źródłowego. Jednakże w klasie pochodnej można ukryć funkcjonalność istniejącą w klasie bazowej, tak aby nie można było uzyskać do niej dostępu poprzez klasę pochodną. Można to zrobić po prostu zmieniając odpowiedni specyfikator dostępu.

Na przykład możemy ustawić element publiczny jako prywatny:

#include <iostream>

class Base
{
public:
	int m_value{};
};

class Derived : public Base
{
private:
	using Base::m_value;

public:
	Derived(int value) : Base { value }
	{
	}
};

int main()
{
	Derived derived{ 7 };
	std::cout << derived.m_value; // error: m_value is private in Derived

	Base& base{ derived };
	std::cout << base.m_value; // okay: m_value is public in Base

	return 0;
}

To pozwoliło nam wziąć źle zaprojektowaną klasę bazową i hermetyzować jej dane w naszej klasie pochodnej. Alternatywnie, zamiast dziedziczyć publicznie elementy Base i uczynić m_value prywatnym przez zastąpienie jego specyfikatora dostępu, moglibyśmy odziedziczyć Base prywatnie, co spowodowałoby, że w pierwszej kolejności wszystkie elementy Base byłyby dziedziczone prywatnie.

Warto jednak zauważyć, że chociaż m_value jest prywatny w klasie Derived, to nadal jest publiczny w klasie Base. Dlatego enkapsulację m_value w Derived można w dalszym ciągu obalić, rzutując na Base& i uzyskując bezpośredni dostęp do elementu członkowskiego.

Dla zaawansowanych czytelników

Z tego samego powodu, jeśli klasa Base ma publiczną funkcję wirtualną, a klasa Derived zmienia specyfikator dostępu na prywatny, publiczność nadal może uzyskać dostęp do prywatnej funkcji Derived, rzutując obiekt Derived na Base& i wywołując funkcję wirtualną. Kompilator na to pozwoli, ponieważ funkcja jest publiczna w Base. Ponieważ jednak obiekt jest w rzeczywistości pochodną, ​​rozpoznawanie funkcji wirtualnej spowoduje rozpoznanie (i wywołanie) (prywatnej) pochodnej wersji funkcji. Kontrola dostępu nie jest egzekwowana w czasie wykonywania.

#include <iostream>

class A
{
public:
    virtual void fun()
    {
        std::cout << "public A::fun()\n";
    }
};

class B : public A
{
private:
    virtual void fun()
    {
         std::cout << "private B::fun()\n";
   }
};

int main()
{
    B b {};
    b.fun();                  // compile error: not allowed as B::fun() is private
    static_cast<A&>(b).fun(); // okay: A::fun() is public, resolves to private B::fun() at runtime

    return 0;
}

Być może zaskakujące, biorąc pod uwagę zestaw przeciążonych funkcji w klasie bazowej, nie ma możliwości zmiany specyfikatora dostępu dla pojedynczego przeciążenia. Możesz tylko zmienić je wszystkie:

#include <iostream>

class Base
{
public:
    int m_value{};

    int getValue() const { return m_value; }
    int getValue(int) const { return m_value; }
};

class Derived : public Base
{
private:
	using Base::getValue; // make ALL getValue functions private

public:
	Derived(int value) : Base { value }
	{
	}
};

int main()
{
	Derived derived{ 7 };
	std::cout << derived.getValue();  // error: getValue() is private in Derived
	std::cout << derived.getValue(5); // error: getValue(int) is private in Derived

	return 0;
}

Usuwanie funkcji w klasie pochodnej

Możesz także oznaczyć funkcje składowe jako usunięte w klasie pochodnej, co gwarantuje, że nie będzie można ich w ogóle wywołać poprzez obiekt pochodny:

#include <iostream>
class Base
{
private:
	int m_value {};

public:
	Base(int value)
		: m_value { value }
	{
	}

	int getValue() const { return m_value; }
};

class Derived : public Base
{
public:
	Derived(int value)
		: Base { value }
	{
	}


	int getValue() const = delete; // mark this function as inaccessible
};

int main()
{
	Derived derived { 7 };

	// The following won't work because getValue() has been deleted!
	std::cout << derived.getValue();

	return 0;
}

W powyższym przykładzie oznaczyliśmy funkcję getValue() jako usuniętą. Oznacza to, że kompilator będzie narzekał, gdy spróbujemy wywołać pochodną wersję funkcji. Należy jednak pamiętać, że podstawowa wersja getValue() jest nadal dostępna. Możemy wywołać Base::getValue() na jeden z dwóch sposobów:

int main()
{
	Derived derived { 7 };

	// We can call the Base::getValue() function directly
	std::cout << derived.Base::getValue();

	// Or we can upcast Derived to a Base reference and getValue() will resolve to Base::getValue()
	std::cout << static_cast<Base&>(derived).getValue();

	return 0;
}

Jeśli używasz metody rzutowania, rzutujemy na Base&, a nie na Base, aby uniknąć tworzenia kopii części Base z derived.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
125 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze