friend class kann nicht ohne namespace angegeben werden?



  • Hallo liebe Forengemeinde,

    ich sitze diesen Abend an einer "cannot access protected member"-Fehlermeldung, die zunächst aus dem Zusammenspiel von Vererbung, typecasting und friend class - Deklaration herzurühren schien.
    Wie mir nun aufgefallen ist, lag es einzig daran, dass zunächst nur

    friend class C;
    

    benutzt wurde. setzt man korrekterweise :: für den 'global namespace' ein, so sieht der code aus wie folgt:

    #include "stdafx.h"
    #include <iostream>
    
    using namespace std;
    
    class C;
    
    namespace ab
    {
    	class A
    	{
    		friend class ::C;
    
    	protected:
    		int x;
    
    		void MethodUsedInC() 
    		{
    			cout << "blah blah" << x << endl;
    	    };
    	};
    
    	class B : public A
    	{
    		friend class ::C;
    	};
    };
    
    class C // 'global namespace' ?
    {
    	public:
    		C()
    		{
    			ref = new ab::B;
    		};
    		void tryToAccess()
    		{
    			ref->x = 10;
    			ref->MethodUsedInC();
    		};
    	private:
    		ab::A* ref;
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	 C inst;
    	 inst.tryToAccess();
    
    	 cout << "Hello World" << endl;
    	 system("PAUSE");
    
    	return 0;
    }
    

    Hier mein Problem:

    sind beide Klassendefinitionen in unterschiedlichen header-Dateien, so funktioniert die friend class - Deklaration, wenn beide Klassen Teil des global namespace sind.
    Sobald nun aber A und B in einem anderen namespace untergebracht werden ("ab_class.h"),

    #pragma once
    
    #include <iostream>
    using namespace std;
    
    namespace ab
    {
    	class A
    	{
    		friend class ::C; 
    
    	protected:
    		int x;
    
    		void MethodUsedInC() 
    		{
    			cout << "blah blah" << x << endl;
    	    };
    	};
    
    	class B : public A
    	{
    		friend class ::C;
    	};
    };
    

    wird es zwingend notwendig, die in Bezug genommene Klasse C vor dem include anzumelden ("c_class.h"):

    #pragma once
    
    class C; // zwingend erforderlich?! 
    
    #include "ab_class.h"
    
    class C // member of 'global namespace'
    {
    	public:
    		C()
    		{
    			ref = new ab::B;
    		};
    		void tryToAccess()
    		{
    			ref->x = 10;
    			ref->MethodUsedInC();
    		};
    	private:
    		ab::A* ref;
    };
    

    entfernt man die namespace ab - Zuweisung und die globale namespace-Angabe ::, kann man das

    class C;
    

    weglassen.

    WARUM?! "class_ab.h" kann dadurch nicht mehr an beliebiger Stelle genutzt werden, da dem #include stets die Einführung der in Bezug genommenen Klasse vorangestellt werden muss.

    Soweit ich es überblicke, kann man dadurch "ab_class.h" in eine der header einbinden, die "c_class.h" wiederum nutzt.

    Ist das ernst gemeint oder übersehe ich etwas - und wenn ja, warum?



  • Edit: sorry, vertippt!

    Soweit ich es überblicke, kann man dadurch "ab_class.h" in KEINE der header einbinden, die "c_class.h" wiederum nutzt.



  • Tjo das mit den friends...

    Wenn du einfach nur "friend class C;" schreibst, dann bezieht sich das auf den "enclosing namespace". In deinem Fall also "ab::C". Dass "ab::C" (noch) nicht deklariert wurde, macht dabei nix, das ist explizit erlaubt.

    Wenn du "::C" meinst, dann musst du dummerweise auch "::C" schreiben. Und davon eine forward-declaration von "::C" machen, weil du sonst nen Error bekommst.

    Wieso machst du es nicht so?

    #pragma once
    
    #include <iostream>
    //using namespace std; // Bitte nicht in Header Files
    
    class C; // Stört ja nicht. Damit sagen wir nur "es gibt eine Klasse C" - das tut ja nicht weh (und was sich reimt ist gut oder so)
    
    namespace ab
    {
        class A
        {
            friend class ::C;  // Jetzt OK
    
        protected:
            int x;
    
            void MethodUsedInC() 
            {
                std::cout << "blah blah" << x << endl;
            };
        };
    
        class B : public A
        {
            friend class ::C;
        };
    };
    


  • Vielen Dank für die rasche Antwort!
    Ich kann zwar noch nicht so ganz akzeptieren, dass eine forward declaration vollkommen legitim ist, wenn keine eigentliche Deklaration folgt, aber es löst das Problem... 🙂

    Worin unterscheidet sich nun die Variante 1

    * forward declaration von C
    * bezugnehmende Klassen in namespace ab
    * friend class ::C für global namespace

    von Variante 2 ?

    * keine forward declaration von C
    * bezugnehmende Klassen ohne benannten Namespace ( = automatisch global? )
    * friend class C

    Sprich: woher rührt nun überhaupt der Zwang zur forward declaration, wenn sie ohne eigenen namespace um A und B nicht erforderlich ist?



  • hboy schrieb:

    Ich kann zwar noch nicht so ganz akzeptieren, dass eine forward declaration vollkommen legitim ist, wenn keine eigentliche Deklaration folgt,

    Warum nicht? Im Header von Klasse A ist die Definition von C völlig uninteressant. Auch die friend-Deklaration ist uninteressant. Sie muss nur syntaktisch korrekt sein, damit der Compiler nicht stolpert. An der Stelle merkt er sich aber eh nur "ok, A hat n Freund". Erst bei der Definition der Methode(n) von C, die auf die geschützten Methoden von A zugreifen, fällt ihm auf "Hey, das ist geschützt, darf es das? Ok, A hat es als Freund deklariert, es darf".

    Grundsätzlich sind Deklarationen ohne passende Definition hinterher sowas von üblich - schau dir mal die ganzen Header an. Die meisten deklarieren einen Haufen von Funktionen und Methoden. Wenn die nicht grade inline definiert werden, werden diese Funktionen in allen bis auf genau einer Übersetzungseinheit, die den Header einbinden, nur deklariert und nicht definiert. Das ist mit Klassen nichts anderes. Dient alles nur dazu, dem Compiler zu sagen "hey, merk dir mal das Symbol hier, nicht dass du dich später wunderst wenn ich es benutze".

    Worin unterscheidet sich nun die Variante 1
    von Variante 2 ?

    Variante 2 beeutet wie du richtig angenommen hast, dass auch Klasse A im globalen namespace liegt. Friend-Deklarationen ohne voll qualifizierten Namen (d.h. ohne Nennung des Namespace) gehen davon aus, dass die genannte Klasse (C) im gleichen Namespace liegt wie die nennde Klasse (A). [Das ist der Grund, warum du in Variante 2 C statt ::C nennen kannst, da C und A beide im globalen ns liegen]. Wenn so eine Klasse vorher noch nicht deklariert wurde, geschieht das implizit. [Das ist der Grund, warum Variante 2 ohne forward-Deklaration funktioniert]
    Liegt die genannte Klasse in einem anderen Namespace muss sie also voll qualifiziert werden [also ::C], und da die implizite Namens-"injektion" nur im gleichen Namespace funktioniert, muss sie dann auch vorher (forward-)deklariert werden.


Anmelden zum Antworten