Perché posso usare auto su un tipo privato?


139

Sono stato in qualche modo sorpreso che il seguente codice venga compilato ed eseguito (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;
}

È corretto che questo codice venga compilato correttamente? E perché è corretto? Perché posso usare autoun tipo privato, mentre non posso usare il suo nome (come previsto)?


11
Osserva anche che f.Baz().iva bene così com'è std::cout << typeid(f.Baz()).name(). Il codice esterno alla classe può "vedere" il tipo restituito Baz()se puoi ottenerlo, semplicemente non puoi nominarlo.
— Steve Jessop,

2
E se pensi che sia strano (cosa che probabilmente fai, visto che te lo stai chiedendo) non sei l'unico;) Questa strategia è tuttavia utilissima per cose come l' idioma di sicurezza .
— Matthieu M.,

2
Penso che la cosa da ricordare è che privatec'è una comodità per descrivere le API in un modo che il compilatore può aiutare a far rispettare. Non ha lo scopo di impedire l'accesso al tipo Barda parte degli utenti Foo, quindi non impedisce Fooin alcun modo di offrire tale accesso restituendo un'istanza di Bar.
— Steve Jessop,

1
"È corretto che questo codice venga compilato correttamente?" No. È necessario #include <iostream>. ;-)
— LF

Risposte:


113

Le regole per autosono, per la maggior parte, le stesse della detrazione del tipo di modello. L'esempio pubblicato funziona per lo stesso motivo per cui è possibile passare oggetti di tipo privato a funzioni modello:

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

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

E perché possiamo passare oggetti di tipo privato alle funzioni del modello, ci chiedi? Perché solo il nome del tipo è inaccessibile. Il tipo stesso è ancora utilizzabile, motivo per cui è possibile restituirlo al codice client.


32
E per vedere che la privacy del nome non ha nulla a che fare con il tipo , aggiungi public: typedef Bar return_type_from_Baz;alla classe Foonella domanda. Ora il tipo può essere identificato da un nome pubblico, nonostante sia definito in una sezione privata della classe.
— Steve Jessop,

1
Per ripetere il punto di @ Steve: l'identificatore di accesso per il nome non ha nulla a che fare con il suo tipo , come visto aggiungendo private: typedef Bar return_type_from_Baz;a Foo, come dimostrato . typedefgli identificatori sono ignari dell'accesso agli specificatori, pubblici e privati.
— Damienh,

Questo non ha senso per me. Il nome del tipo è semplicemente un alias per il tipo effettivo. Cosa importa se lo chiamo Baro SomeDeducedType? Non è che posso usarlo per raggiungere membri privati class Fooo altro.
— einpoklum,

107

Il controllo dell'accesso viene applicato ai nomi . Confronta con questo esempio dallo 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
}

12

A questa domanda ha già risposto molto bene sia il freddo che R. Martinho Fernandes.

Non potevo lasciarmi sfuggire l'opportunità di rispondere a una domanda con un'analogia di Harry Potter:

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

Grazie a Quentin per avermi ricordato la scappatoia di Harry.


5
Non friend class Harry;manca un dentro?
— Quentin,

@Quentin hai assolutamente ragione! Per completezza si dovrebbe probabilmente aggiungere anche friend class Dumbledore;;)
— jpihl

Harry non mostra di non aver paura chiamando il Wizard::LordVoldemort;moderno C ++. Invece, chiama using Wizard::LordVoldemort;. (Non è così naturale usare Voldemort, onestamente. ;-)
— LF

8

Per aggiungere alle altre (buono) risposte, ecco un esempio da C ++ 98 che illustra che la questione in realtà non ha a che fare con autoaffatto

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
}

L'uso del tipo privato non è proibito, stava solo nominando il tipo. La creazione di un temporaneo senza nome di quel tipo va bene, ad esempio, in tutte le versioni di C ++.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.