Warum kann ich auto für einen privaten Typ verwenden?

139

Ich war irgendwie überrascht, dass der folgende Code kompiliert und ausgeführt wird (vc2012 & gcc4.7.2)

class Foo {
    struct Bar { int i; };
public:
    Bar Baz() { return Bar(); }
};

int main() {
    Foo f;
    // Foo::Bar b = f.Baz();  // error
    auto b = f.Baz();         // ok
    std::cout << b.i;
}

Ist es richtig, dass dieser Code gut kompiliert wird? Und warum ist es richtig? Warum kann ich autoeinen privaten Typ verwenden, während ich seinen Namen nicht verwenden kann (wie erwartet)?

hansmaad
quelle
11
Beachten Sie, dass dies f.Baz().iauch in Ordnung ist std::cout << typeid(f.Baz()).name(). Code außerhalb der Klasse kann den zurückgegebenen Typ "sehen", Baz()wenn Sie ihn erhalten können, können Sie ihn einfach nicht benennen.
Steve Jessop
2
Und wenn du denkst, dass es komisch ist (was du wahrscheinlich tust, wenn du danach fragst), bist du nicht der einzige;) Diese Strategie ist jedoch sehr nützlich für Dinge wie das Safe-Bool-Idiom .
Matthieu M.
2
Ich denke, die Sache, an die man sich erinnern sollte, ist, dass privatees eine Annehmlichkeit gibt, APIs so zu beschreiben, dass der Compiler sie durchsetzen kann. Es ist nicht beabsichtigt, den Zugriff Barvon Benutzern auf den Typ zu verhindern Foo, daher wird es Fooin keiner Weise daran gehindert, diesen Zugriff durch Rückgabe einer Instanz von anzubieten Bar.
Steve Jessop
1
"Ist es richtig, dass dieser Code gut kompiliert wird?" Nein, das musst du #include <iostream>. ;-)
LF

Antworten:

113

Die Regeln für autosind größtenteils dieselben wie für den Abzug von Vorlagentypen. Das veröffentlichte Beispiel funktioniert aus demselben Grund, aus dem Sie Objekte privaten Typs an Vorlagenfunktionen übergeben können:

template <typename T>
void fun(T t) {}

int main() {
    Foo f;
    fun(f.Baz());         // ok
}

Und warum können wir Objekte privaten Typs an Vorlagenfunktionen übergeben, fragen Sie? Weil nur auf den Namen des Typs nicht zugegriffen werden kann. Der Typ selbst ist weiterhin verwendbar, weshalb Sie ihn überhaupt an den Clientcode zurückgeben können.

R. Martinho Fernandes
quelle
32
Und um zu sehen, dass die Privatsphäre des Namens nichts mit dem Typ zu tun hat , fügen Sie public: typedef Bar return_type_from_Baz;der Klasse Fooin der Frage hinzu. Jetzt kann der Typ durch einen öffentlichen Namen identifiziert werden, obwohl er in einem privaten Abschnitt der Klasse definiert ist.
Steve Jessop
1
Um @ Steves Punkt zu wiederholen: Der Zugriffsspezifizierer für den Namen hat nichts mit seinem Typ zu tun , wie durch Hinzufügen private: typedef Bar return_type_from_Baz;zu gezeigt Foo, wie gezeigt . typedefBezeichner haben keinen Zugriff auf öffentliche und private Bezeichner.
Damienh
Das macht für mich keinen Sinn. Der Name des Typs ist lediglich ein Alias ​​für den tatsächlichen Typ. Was macht es aus, wenn ich es nenne Baroder SomeDeducedType? Es ist nicht so, dass ich es benutzen kann, um zu privaten Mitgliedern von class Foooder irgendetwas zu gelangen.
Einpoklum
107

Die Zugriffskontrolle wird auf Namen angewendet . Vergleichen Sie mit diesem Beispiel aus dem Standard:

class A {
  class B { };
public:
  typedef B BB;
};

void f() {
  A::BB x; // OK, typedef name A::BB is public
  A::B y; // access error, A::B is private
}
Ausruhen
quelle
12

Diese Frage wurde sowohl von chill als auch von R. Martinho Fernandes bereits sehr gut beantwortet.

Ich konnte die Gelegenheit einfach nicht verpassen, eine Frage mit einer Harry-Potter-Analogie zu beantworten:

class Wizard
{
private:
    class LordVoldemort
    {
        void avada_kedavra()
        {
            // scary stuff
        }
    };
public:
    using HeWhoMustNotBeNamed = LordVoldemort;

    friend class Harry;
};

class Harry : Wizard
{
public:
    Wizard::LordVoldemort;
};

int main()
{
    Wizard::HeWhoMustNotBeNamed tom; // OK
    // Wizard::LordVoldemort not_allowed; // Not OK
    Harry::LordVoldemort im_not_scared; // OK
    return 0;
}

https://ideone.com/I5q7gw

Vielen Dank an Quentin, der mich an die Harry-Lücke erinnert hat.

jpihl
quelle
5
Fehlt da nicht ein friend class Harry;?
Quentin
@ Quentin du bist absolut richtig! Der Vollständigkeit friend class Dumbledore;
halber
Harry zeigt nicht, dass er keine Angst hat, wenn er Wizard::LordVoldemort;modernes C ++ anruft . Stattdessen ruft er an using Wizard::LordVoldemort;. (Es fühlt sich nicht so natürlich an, Voldemort zu benutzen, ehrlich. ;-)
LF
8

So fügen Sie zu den anderen (gut) Antworten, hier ist ein Beispiel von C ++ 98 , die zeigt , dass das Problem wirklich nicht mit dem zu tun hat autoüberhaupt

class Foo {
  struct Bar { int i; };
public:
  Bar Baz() { return Bar(); }
  void Qaz(Bar) {}
};

int main() {
  Foo f;
  f.Qaz(f.Baz()); // Ok
  // Foo::Bar x = f.Baz();
  // f.Qaz(x);
  // Error: error: ‘struct Foo::Bar’ is private
}

Die Verwendung des privaten Typs ist nicht verboten, es wurde nur der Typ benannt. Das Erstellen einer unbenannten temporären Datei dieses Typs ist beispielsweise in allen Versionen von C ++ in Ordnung.

Chris Beck
quelle