Mengapa saya dapat menggunakan otomatis pada tipe pribadi?


139

Entah bagaimana saya terkejut bahwa kode berikut mengkompilasi dan menjalankan (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;
}

Apakah benar kode ini mengkompilasi dengan baik? Dan mengapa itu benar? Mengapa saya bisa menggunakan autotipe pribadi, sementara saya tidak bisa menggunakan namanya (seperti yang diharapkan)?


11
Perhatikan itu f.Baz().ijuga OK, seperti apa adanya std::cout << typeid(f.Baz()).name(). Kode di luar kelas dapat "melihat" jenis yang dikembalikan oleh Baz()jika Anda dapat memperolehnya, Anda tidak dapat menyebutkannya.
Steve Jessop

2
Dan jika Anda berpikir itu aneh (yang mungkin Anda lakukan, melihat ketika Anda bertanya tentang hal itu) Anda bukan satu-satunya;) Strategi ini sangat berguna untuk hal-hal seperti Idiom Safe-Bool .
Matthieu M.

2
Saya pikir hal yang perlu diingat adalah bahwa privateada kemudahan untuk menggambarkan API dengan cara yang dapat dikompilasi oleh kompiler. Itu tidak dimaksudkan untuk mencegah akses ke tipe Baroleh pengguna Foo, jadi itu tidak menghalangi Foodengan cara apapun dari menawarkan akses itu dengan mengembalikan instance Bar.
Steve Jessop

1
"Apakah benar kode ini dikompilasi dengan baik?" Tidak. Kamu perlu #include <iostream>. ;-)
LF

Jawaban:


113

Aturan untuk autoadalah, untuk sebagian besar, sama dengan pengurangan jenis template. Contoh yang diposting berfungsi karena alasan yang sama Anda bisa meneruskan objek tipe pribadi ke fungsi templat:

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

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

Dan mengapa kita bisa meneruskan objek tipe pribadi ke fungsi templat, Anda bertanya? Karena hanya nama jenisnya yang tidak bisa diakses. Jenisnya sendiri masih dapat digunakan, itulah mengapa Anda dapat mengembalikannya ke kode klien sama sekali.


32
Dan untuk melihat bahwa privasi nama tidak ada hubungannya dengan tipe , tambahkan public: typedef Bar return_type_from_Baz;ke kelas Foodalam pertanyaan. Sekarang tipe dapat diidentifikasi dengan nama publik, meskipun didefinisikan di bagian pribadi kelas.
Steve Jessop

1
Untuk mengulang @ titik Steve: akses specifier untuk nama tidak ada hubungannya dengan itu tipe , seperti yang terlihat dengan menambahkan private: typedef Bar return_type_from_Baz;ke Foo, sebagai ditunjukkan . typedefPengidentifikasi lupa untuk mengakses specifier, publik dan pribadi.
damienh

Ini tidak masuk akal bagi saya. The nama dari jenis hanyalah sebuah alias untuk jenis yang sebenarnya. Apa bedanya jika saya menyebutnya Baratau SomeDeducedType? Bukannya saya bisa menggunakannya untuk mendapatkan anggota pribadi class Fooatau apa pun.
einpoklum

107

Kontrol akses diterapkan ke nama . Bandingkan dengan contoh ini dari standar:

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

Pertanyaan ini telah dijawab dengan sangat baik oleh chill dan R. Martinho Fernandes.

Saya tidak bisa melewatkan kesempatan untuk menjawab pertanyaan dengan analogi 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

Terima kasih kepada Quentin karena mengingatkanku akan celah Harry.


5
Bukankah ada yang friend class Harry;hilang di sana?
Quentin

@ Quentin kamu benar sekali! Untuk kelengkapan, orang mungkin juga harus menambahkan friend class Dumbledore;;)
jpihl

Harry tidak menunjukkan bahwa dia tidak takut dengan memanggil Wizard::LordVoldemort;C ++ modern. Sebaliknya, dia memanggil using Wizard::LordVoldemort;. (Jujur saja, rasanya tidak wajar menggunakan Voldemort. ;-)
LF

8

Untuk menambah jawaban (baik) lainnya, berikut adalah contoh dari C ++ 98 yang menggambarkan bahwa masalah sebenarnya tidak ada hubungannya autosama sekali

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
}

Menggunakan tipe pribadi tidak dilarang, itu hanya penamaan tipe. Membuat temporer yang tidak bernama dari jenis itu boleh saja, misalnya, di semua versi C ++.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.