Mit upcast eine private Basis "entblößen"?
-
Bitbandit schrieb:
SeppJ schrieb:
Häh? Natürlich kann Child auf Base::get() zugreifen.
Hier greift aber nicht Child auf Base::get() zu, sondern die Funktion main.
Ja, da war ich zu schnell. Das üblich vorgehen ist hier folgendes:
#include <iostream> class Base { private: int Number; public: Base() { Number = 100; } int get() { return Number; } }; class Child : private Base { public: using Base::get; }; int main() { Child Object; std::cout << Object.get(); // jetzt möglich return 0; }Das mit dem rumcasten würde ich lieber sein lassen, dies kann zu allerlei Verwirrung führen, zum Beispiel bei Mehrfachvererbung.
-
Das Verhalten ist korrekt.
-
SeppJ schrieb:
Das üblich vorgehen ist hier folgendes:
#include <iostream> class Base { private: int Number; public: Base() { Number = 100; } int get() { return Number; } }; class Child : private Base { public: using Base::get; }; int main() { Child Object; std::cout << Object.get(); // jetzt möglich return 0; }Vielen Dank für diesen Hinweis. In meinem Fall geht das allerdings nicht, da ich es ja so möchte, dass man von "außen" nicht an die Methoden von Base ran kommt. Es gibt aber eben Ausnahmefälle, wo die Methoden einer bestimmten Klasse dies doch benötigen. Normalerweise würde ich diese Klasse dann als "friend" eintragen, aber das geht dann wieder aus anderen Gründen nicht.
SeppJ schrieb:
Das mit dem rumcasten würde ich lieber sein lassen, dies kann zu allerlei Verwirrung führen, zum Beispiel bei Mehrfachvererbung.
Könntest du das etwas genauer erläutern? Denn in meinem Fall liegt eine Mehrfachvererbung vor.
@knivil:
Vielen Dank für deine Einschätzung. Wie siehst du das mit der Mehrfachvererbung?
-
Möglich ist so etwas durchaus, aber sauber objektorientiert ist es beim besten Willen nicht. Kannst du vielleicht etwas genauer beschreiben, was du eigentlich vorhast?
-
Man kann Sicherheitsmechanismen in C++ immer umgehen, wenn man will. Deren Sinn besteht nicht darin, böse Absichten zu verhindern; von daher hast du nur einen Weg gefunden, ein Sprachkonzept zu missbrauchen. Das ist nicht irgendwie spektakulär oder so.
-
Bitbandit schrieb:
Könntest du das etwas genauer erläutern? Denn in meinem Fall liegt eine Mehrfachvererbung vor.
es hilft, immer auf alles vorbereitet zu sein. Es würde schon gehen, aber es wäre halt umständlich.
Vielen Dank für diesen Hinweis. In meinem Fall geht das allerdings nicht, da ich es ja so möchte, dass man von "außen" nicht an die Methoden von Base ran kommt. Es gibt aber eben Ausnahmefälle, wo die Methoden einer bestimmten Klasse dies doch benötigen. Normalerweise würde ich diese Klasse dann als "friend" eintragen, aber das geht dann wieder aus anderen Gründen nicht.
Genau dafür ist friend doch da, warum geht das nicht?
-
seldon schrieb:
Möglich ist so etwas durchaus, aber sauber objektorientiert ist es beim besten Willen nicht. Kannst du vielleicht etwas genauer beschreiben, was du eigentlich vorhast?
Hmmm, ich versuche es. Es ist allerdings etwas komplexer

Ich habe eine Klasse, welche diverse "Service-Funktionalitäten" für verschiedene Programme bereitstellen soll. Diese Klasse setzt sich durch Mehrfachvererbung aus 4 großen "Modulklassen" zusammen, welche jede eine bestimmte interne Funktionalität für die eigentliche Klasse beisteuern. (*1)
Die eigentliche Klasse besitzt dann öffentliche Methoden, welche eine Art externes Interface nach außen hin bereitstellen. Die öffentlichen Methoden der Modulklassen werden durch die private Vererbung nach außen hin verdeckt, dies ist auch so gewünscht.
In speziellen Fällen soll der Zugriff auf das "interne Interface" einer Modulklasse jedoch möglich sein (z.B. für Tests) (*2). Allerdings ist der "Serviceklasse" diese Testklasse nicht bekannt, daher kann sie nicht friend sein. In diesem Fall soll der Zugriff über einen Umweg möglich sein, der prinzipiell dem von Oben entspricht.
(*1) Es ist sichergestellt, dass sich die Namen der Methoden dieser Modulklassen nicht gegenseitig in die Quere kommen. Also eine Modulklasse verdeckt niemals Methoden oder Attribute einer anderen Modulklasse.
(*2) Es ist außerdem sichergestellt, dass die Serviceklasse keine Methoden der Modulklasse überschreibt.
Ich behaupte ja auch nicht, dass dies spektakulär sei. Ich hatte nur bei meinem Versuchen in dieser Richtung ab und zu die Compiler-Meldung, dass ein Upcast zum "Basisobjekt" nicht möglich sei und hier scheint dies durchaus möglich zu sein. Also war/bin ich skeptisch...
-
Mehrfachvererbung ... keine Ahnung, da man schnell undefiniertes Verhalten erzeugen kann. In diesem Fall aber castest du das Objekt auf
Base. Und wenn das Objekt vom Typ Base ist, dann darf ich auch die public-Methoden von Base benutzen.In meinem Fall geht das allerdings nicht, da ich es ja so möchte, dass man von "außen" nicht an die Methoden von Base ran kommt.
Wie immer gern gesagt wird: Encapsulation prevents mistakes, not espionage.
Es gibt aber eben Ausnahmefälle, wo die Methoden einer bestimmten Klasse dies doch benötigen. Normalerweise würde ich diese Klasse dann als "friend" eintragen, aber das geht dann wieder aus anderen Gründen nicht.
Dein Design ist schlecht, ueberarbeite es.
Diese Klasse setzt sich durch Mehrfachvererbung aus 4 großen "Modulklassen" zusammen, welche jede eine bestimmte interne Funktionalität für die eigentliche Klasse beisteuern. (*1)
Bist du sicher, das du hier Vererbung brauchst? Reicht es nicht, die "Modulklassen" einfach als Member zu deklarieren und die Funktionalitaet ausgewaehlter Methoden von aussen einfach an die entsprechende Member zu delegieren?
In speziellen Fällen soll der Zugriff auf das "interne Interface" einer Modulklasse jedoch möglich sein (z.B. für Tests) (*2).
It is broken ... Du kannst die verschiedenen Modulklassen auch einzeln testen. Du kannst nur fuers Testen, die privaten Atribute public machen, du kannst ...
-
Schon mal überlegt, Komposition statt privater Vererbung zu verwenden? Private Vererbung macht nur in Ausnahmefällen Sinn.
-
Ja, die Module als Objekte innerhalb der Klasse habe ich mir auch schon überlegt. Bei den 4 Klassen würde das auf jeden Fall gehen. Allerdings würde dies das Problem nicht lösen, dass deren öffentliche Funktionen in Ausnahmefällen von außen zugänglich sein sollen, ohne dass diese Zugänglichkeit pauschal möglich ist.
Ich bin natürlich für Ratschläge jederzeit dankbar

-
Bitbandit schrieb:
Allerdings würde dies das Problem nicht lösen, dass deren öffentliche Funktionen in Ausnahmefällen von außen zugänglich sein sollen, ohne dass diese Zugänglichkeit pauschal möglich ist.
Warum kannst du für diese Ausnahmefälle nicht öffentliche Funktionen in der umgebenden Klasse implementieren, die die Aufrufe nur an ihre Memberobjekte weiterleitet?
-
Dann verrate doch endlich mal, warum friend unmöglich ist.
-
@Bitbandit:
Du willst also Whitebox-Tests schreiben, wobei die Test-Klasse der zu testenden Klasse nicht bekannt ist (sein kann)?
Das hört sich einigermassen gefährlich an.Eine inhärente Eigenschaft von Whitebox-Tests ist es ja schliesslich, dass sie direkten Zugriff auf die Implementierung von etwas haben, und dadurch (mehr oder weniger) anhängig von dieser Implementierung sind.
Wenn du jetzt sagst dass die Test-Klasse der zu testenden Klasse nicht bekannt ist (sein kann), dann klingt das daher ein wenig gefährlich.
Die Test-Klasse sollte in so einem Fall Teil des Projekts der zu testenden Klasse sein. Ganz einfach deswegen, weil Code der brechen könnte wenn man die Implementierung von etwas ändert, möglichst nahe an dieser Implementierung sein sollte. Konkreter: im Fall von Visual-Studio will ich das auf jeden Fall in der gleichen Solution haben. Andernfalls baut man viel zu Schnell Unsinn. mMn. sind Whitebox-Tests sogar am besten in der selben DLL/LIB aufgehoben wo auch die zu-testenden Klassen leben.Und sobald beide Klassen im selben Projekt verwaltet werden, sehe ich keinen absoluten Grund mehr, warum die Test-Klasse der zu testenden Klasse nicht bekannt sein könnte/dürfte/sollte.
-
Wie man eine private Methode für interne Zwecke sichtbar machen kann, ohne dabei die direkte Schnittstelle der Klasse oder andere Schnittstellen zu verändern:
#include <iostream> namespace p { class Test; namespace detail { void call_private(p::Test const& test); } // detail class Test { private: void call_private() const { std::cout << "private" << std::endl; } friend void detail::call_private(Test const& test); }; namespace detail { void call_private(p::Test const& test) { test.call_private(); } } } // p::detail int main() { p::Test t; p::detail::call_private(t); return 0; }Wenn es nur ein paar Methoden betrifft, kann man die einzelnen Methoden so sichtbar machen. Falls es mehr sein soll, kann eine Methode natürlich auch einen Zeiger oder Referenz zurückgeben, welche auf eine grössere Struktur verweist. Auf detail-Namensräumen sollte ein Programmierer nie zugreifen, dass ist so eine ungeschriebene Richtlinie.
Ich würde aber trotzdem vorschlagen, die Tipps der anderen hier zu berücksichtigen.
Grüssli
-
Hmmm...ich denke, ich sehe die Problematik.
Also, prinzipiell sind Black-Box-Tests sinnvoller. Wenn du nicht die gesamte Funktionalität einer Klasse als Black Box testen kannst, ist die Wahrscheinlichkeit hoch, dass du gut beraten wärst, ihre Funktionalität in kleinere Teile aufzuspalten und diese getrennt zu testen. Wenn das geht, ist das die richtige Lösung.
Schwieriger ist es, wenn es darum geht, Verhalten zu testen, das für den Benutzer nicht direkt von Interesse ist. Konkretes Beispiel: Ich habe vor einer Weile eine Klasse geschrieben, die die Funktionalität eines std::vector nachäfft, aber zunächst einen lokalen Speicherbereich verwendet und erst auf den Heap ausweicht, wenn dieser gefüllt ist - es ging dabei um Vermeidung von Heap Contention zwischen vielen Threads. Der Benutzer sollte sich nicht darum scheren, wo zu bestimmter Zeit die Daten liegen, aber für meine Tests war natürlich von großer Bedeutung, dass diese lokal liegen, solange es möglich ist.
In so einem Fall ist mir keine wirklich saubere Lösung bekannt (wenn jemand eine Idee hat, wäre ich daran sehr interessiert). Geht es nur um begrenzte Information, bei der es keinen wirklich zwingenden Grund gibt, sie dem Benutzer vorzuenthalten, habe ich wenig Probleme damit, sie einfach public zu machen, aber eine private Basisklasse so nach außen zu reichen, untergräbt das ursprüngliche Design, so dass es keine wirkliche Option ist.
Hmm...wir befinden uns im dreckigen Hackland, aber denkbar wäre etwas in dieser Art:
namespace test_impl { class foo_test_accessor; } class foo : private bar { friend class test_impl::foo_test_accessor; };und
namespace test_impl { struct foo_test_accessor { foo_test_accessor(foo &obj) : obj(obj) { } bar &obj; }; }wobei letzteres nur den Testcases bekannt ist. Wenn jemand dieses Schlupfloch nachher ausnutzt, hat man jedenfalls wenig Probleme, ihm zu erklären, dass er selbst schuld ist, wenn sein Code mit einer zukünftigen Version nicht mehr funktioniert.
-
@seldon:
Ich habe eigentlich genau das vor, was du dort vorstellst. Ich bin mir ja selbst auch über viele Gefahren im klaren, aber es ist ehrlich gesagt keine Untergrabung der Kapselung, sondern in meinem Fall eher eine andere Sichtweise auf die gekapselte Klasse. Es ist wie in einem Hotel: Nach vorn raus haben wir den Haupteingang. Dieser ist für die Gäste gedacht. Die Gäste müssen sich nicht dafür interessieren, wie das Hotel intern organisiert ist. Der Vordereingang - bzw. Haupteingang - ist also mein offizielles Interface. Aber jedes Hotel hat auch einen Lieferanteneingang: Hier werden Zutaten für die Küche und Wäsche geliefert sowie abgeholt. Der Lieferant ist aber auch eine "externe" Person, welche sich nicht dafür zu interessieren hat, wie das Hotel intern organisiert wird. Er benötigt allerdings eine andere Schnittstelle als die Hotelgäste und diese Schnittstelle steht für die Hotelgäste auch gar nicht zur Verfügung. Also: Die Kapselung wird gewahrt, nur die Schnittstelle wird um weitere Funktionalitäten ergänzt.Edit: Vielleicht war es doch keine gute Idee mein Projekt hier so detailliert zu beschreiben.
-
Dravere schrieb:
friend void detail::call_private(Test const& test);Veränderst du nicht hierdurch die Schnittstelle?
Ansonsten kann man die Klasse auch gleich als friend eintragen...