Mengapa metode public const tidak dipanggil ketika non-const private?

117

Pertimbangkan kode ini:

struct A
{
    void foo() const
    {
        std::cout << "const" << std::endl;
    }

    private:

        void foo()
        {
            std::cout << "non - const" << std::endl;
        }
};

int main()
{
    A a;
    a.foo();
}

Kesalahan kompilernya adalah:

kesalahan: 'void A :: foo ()' bersifat pribadi`.

Tetapi ketika saya menghapus yang pribadi, itu hanya berfungsi. Mengapa metode public const tidak dipanggil ketika non-const private?

Dengan kata lain, mengapa resolusi kelebihan beban harus ada sebelum kontrol akses? Ini aneh. Apakah menurut Anda itu konsisten? Kode saya berfungsi dan kemudian saya menambahkan metode, dan kode kerja saya tidak dapat dikompilasi sama sekali.

Narek
sumber
3
Dalam C ++, tanpa upaya ekstra seperti penggunaan idiom PIMPL, tidak ada bagian "pribadi" yang nyata dari kelas. Ini hanyalah salah satu masalah (menambahkan metode "pribadi" yang berlebihan dan memecahkan kompilasi kode lama dianggap sebagai masalah dalam buku saya, bahkan jika masalah ini sepele untuk dihindari dengan hanya tidak melakukannya) yang disebabkan olehnya.
hyde
Apakah ada kode kehidupan nyata di mana Anda berharap dapat memanggil fungsi const tetapi rekan non-constnya akan menjadi bagian dari antarmuka pribadi? Ini terdengar seperti desain antarmuka yang buruk bagi saya.
Vincent Fourmond

Jawaban:

125

Saat Anda memanggil a.foo();, kompilator melewati resolusi kelebihan beban untuk menemukan fungsi terbaik untuk digunakan. Saat membangun kumpulan kelebihan yang ditemukannya

void foo() const

dan

void foo()

Sekarang, karena atidak const, versi non-const adalah yang paling cocok, jadi kompilator memilih void foo(). Kemudian pembatasan akses diberlakukan dan Anda mendapatkan kesalahan kompiler, karena void foo()bersifat pribadi.

Ingat, dalam resolusi kelebihan beban, ini bukan 'temukan fungsi terbaik yang dapat digunakan'. Ini adalah 'temukan fungsi terbaik dan coba gunakan'. Jika tidak bisa karena pembatasan akses atau sedang dihapus, maka Anda mendapatkan kesalahan kompiler.

Dengan kata lain mengapa resolusi kelebihan beban harus ada sebelum kontrol akses?

Nah, mari kita simak:

struct Base
{
    void foo() { std::cout << "Base\n"; }
};

struct Derived : Base
{
    void foo() { std::cout << "Derived\n"; }
};

struct Foo
{
    void foo(Base * b) { b->foo(); }
private:
    void foo(Derived * d) { d->foo(); }
};

int main()
{
    Derived d;
    Foo f;
    f.foo(&d);
}

Sekarang katakanlah saya sebenarnya tidak bermaksud menjadikan void foo(Derived * d)pribadi. Jika kontrol akses didahulukan, maka program ini akan dikompilasi dan dijalankan dan Baseakan dicetak. Ini bisa sangat sulit untuk dilacak dalam basis kode yang besar. Karena kontrol akses muncul setelah resolusi kelebihan beban, saya mendapatkan kesalahan kompiler yang bagus yang memberi tahu saya bahwa fungsi yang saya ingin panggil tidak dapat dipanggil, dan saya dapat menemukan bug dengan lebih mudah.

NathanOliver
sumber
Apakah ada alasan mengapa kontrol akses setelah resolusi yang berlebihan?
drake7707
3
@ drake7707 Wll seperti yang saya tunjukkan dalam contoh kode saya Anda jika kontrol akses datang lebih dulu maka kode di atas akan dikompilasi, yang mengubah semantik program. Tidak yakin tentang Anda, tetapi saya lebih suka mengalami kesalahan dan perlu melakukan cast eksplisit jika saya ingin fungsi tetap pribadi, maka cast implisit dan kode diam-diam "berfungsi".
NathanOliver
"dan perlu melakukan cast eksplisit jika saya ingin fungsinya tetap pribadi" - sepertinya masalah sebenarnya di sini adalah cast implisit ... meskipun di sisi lain, gagasan bahwa Anda juga dapat menggunakan kelas turunan secara implisit sebagai kelas dasar merupakan ciri khas paradigma OO, bukan?
Steven Byks
35

Pada akhirnya, hal ini bermuara pada pernyataan dalam standar bahwa aksesibilitas tidak boleh dipertimbangkan saat melakukan resolusi kelebihan beban . Penegasan ini dapat ditemukan di [over.match] klausa 3:

... Ketika resolusi kelebihan beban berhasil, dan fungsi terbaik yang layak tidak dapat diakses (Klausul [class.access]) dalam konteks yang digunakan, program menjadi tidak benar.

dan juga Catatan di klausul 1 dari bagian yang sama:

[Catatan: Fungsi yang dipilih oleh resolusi kelebihan beban tidak dijamin sesuai untuk konteksnya. Pembatasan lain, seperti aksesibilitas fungsi, dapat membuat penggunaannya dalam konteks pemanggilan tidak tepat. - catatan akhir]

Untuk alasannya, saya dapat memikirkan beberapa kemungkinan motivasi:

  1. Ini mencegah perubahan perilaku yang tidak terduga sebagai akibat dari mengubah aksesibilitas kandidat yang kelebihan beban (sebagai gantinya, kesalahan kompilasi akan terjadi).
  2. Ini menghilangkan ketergantungan konteks dari proses resolusi kelebihan beban (yaitu resolusi yang berlebihan akan memiliki hasil yang sama baik di dalam atau di luar kelas).
atkins
sumber
32

Misalkan kontrol akses datang sebelum resolusi yang berlebihan. Secara efektif, ini berarti public/protected/privatevisibilitas terkontrol daripada aksesibilitas.

Bagian 2.10 dari Desain dan Evolusi C ++ oleh Stroustrup memiliki bagian tentang ini di mana dia membahas contoh berikut

int a; // global a

class X {
private:
    int a; // member X::a
};

class XX : public X {
    void f() { a = 1; } // which a?
};

Stroustrup menyebutkan bahwa keuntungan dari aturan saat ini (visibilitas sebelum aksesibilitas) adalah bahwa (sementara) mengubah bagian privatedalamnya class Xmenjadi public(misalnya untuk tujuan debugging) adalah bahwa tidak ada perubahan diam-diam dalam arti dari program di atas (yaitu X::amencoba untuk diakses dalam kedua kasus, yang memberikan kesalahan akses pada contoh di atas). Jika public/protected/privateakan mengontrol visibilitas, arti program akan berubah (global aakan disebut dengan private, jika tidak X::a).

Dia kemudian menyatakan bahwa dia tidak ingat apakah itu dengan desain eksplisit atau efek samping dari teknologi preprocessor yang digunakan untuk mengimplementasikan C dengan pendahulu Classess ke Standar C ++.

Bagaimana ini terkait dengan teladan Anda? Pada dasarnya karena Standar membuat resolusi kelebihan beban sesuai dengan aturan umum bahwa pencarian nama dilakukan sebelum kontrol akses.

10.2 Pencarian nama anggota [class.member.lookup]

1 Pencarian nama anggota menentukan arti nama (ekspresi-id) dalam ruang lingkup kelas (3.3.7). Pencarian nama dapat mengakibatkan ambiguitas, dalam hal ini program memiliki format yang salah. Untuk ekspresi-id, pencarian nama dimulai dalam lingkup kelas this; untuk id yang memenuhi syarat, pencarian nama dimulai dalam cakupan penentu nama bersarang. Pencarian nama dilakukan sebelum kontrol akses (3.4, Klausul 11).

8 Jika nama fungsi yang kelebihan beban ditemukan dengan jelas, resolusi kelebihan muatan (13.3) juga terjadi sebelum kontrol akses . Ambiguitas sering kali dapat diatasi dengan membuat kualifikasi nama dengan nama kelasnya.

TemplateRex
sumber
23

Karena thispointer implisit adalah non- const, kompilator pertama-tama akan memeriksa keberadaan fungsi bukan constversi sebelum constversi.

Jika Anda secara eksplisit menandai non- constone privatemaka resolusi akan gagal, dan kompilator tidak akan melanjutkan pencarian.

Batsyeba
sumber
Apakah menurut Anda itu konsisten? Kode saya berfungsi dan kemudian saya menambahkan metode dan kode kerja saya tidak dapat dikompilasi sama sekali.
Narek
Saya pikir begitu. Resolusi overload sengaja dibuat rewel. Saya menjawab pertanyaan serupa kemarin: stackoverflow.com/questions/39023325/…
Batsyeba
5
@Narek Saya percaya ini bekerja seperti bagaimana fungsi yang dihapus lakukan dalam resolusi yang berlebihan. Ia mengambil yang terbaik dari set dan kemudian melihatnya tidak tersedia sehingga Anda mendapatkan kesalahan kompiler. Itu tidak memilih fungsi terbaik yang dapat digunakan tetapi fungsi terbaik dan kemudian mencoba menggunakannya.
NathanOliver
3
@ Narek Saya juga pertama kali bertanya-tanya mengapa itu tidak berhasil, tetapi pertimbangkan ini: bagaimana Anda akan memanggil fungsi pribadi jika konstanta publik harus dipilih juga untuk objek non-konst?
idclev 463035818
20

Penting untuk diingat urutan hal-hal yang terjadi, yaitu:

  1. Temukan semua fungsi yang layak.
  2. Pilih fungsi terbaik yang layak.
  3. Jika tidak ada satu pun yang dapat dijalankan terbaik, atau jika Anda tidak dapat benar-benar memanggil fungsi terbaik yang dapat dijalankan (karena pelanggaran akses atau fungsi yang deleted), gagal.

(3) terjadi setelah (2). Yang sangat penting, karena jika tidak membuat fungsi deleted atau privateakan menjadi semacam tidak berarti dan jauh lebih sulit untuk dipikirkan.

Pada kasus ini:

  1. Fungsi yang layak adalah A::foo()dan A::foo() const.
  2. Fungsi terbaik yang dapat dijalankan adalah A::foo()karena yang terakhir melibatkan konversi kualifikasi pada thisargumen implisit .
  3. Tetapi A::foo()adalah privatedan Anda tidak memiliki akses ke sana, oleh karena itu kodenya salah bentuk.
Barry
sumber
1
Orang mungkin berpikir "layak" akan mencakup pembatasan akses yang relevan. Dengan kata lain, tidak "layak" untuk memanggil fungsi privat dari luar kelas, karena ini bukan bagian dari antarmuka publik kelas tersebut.
RM
15

Ini tergantung pada keputusan desain yang cukup mendasar di C ++.

Saat mencari fungsi untuk memenuhi panggilan, kompilator melakukan pencarian seperti ini:

  1. Ini mencari untuk menemukan 1 lingkup pertama di mana ada sesuatu dengan nama itu.

  2. Kompilator menemukan semua fungsi (atau functor, dll.) Dengan nama itu dalam cakupan itu.

  3. Kemudian kompilator melakukan overload resolusi untuk menemukan kandidat terbaik di antara mereka yang ditemukan (apakah mereka dapat diakses atau tidak).

  4. Akhirnya, kompilator memeriksa apakah fungsi yang dipilih itu dapat diakses.

Karena pengurutan itu, ya, mungkin saja compiler akan memilih kelebihan muatan yang tidak dapat diakses, meskipun ada kelebihan muatan lain yang dapat diakses (tetapi tidak dipilih selama resolusi kelebihan muatan).

Mengenai apakah mungkin melakukan sesuatu secara berbeda: ya, tidak diragukan lagi itu mungkin. Ini pasti akan mengarah ke bahasa yang sangat berbeda dari C ++. Ternyata banyak keputusan yang tampaknya kecil dapat memiliki konsekuensi yang mempengaruhi lebih banyak daripada yang mungkin terlihat pada awalnya.


  1. "Pertama" bisa menjadi sedikit rumit, terutama jika / jika template terlibat, karena dapat mengarah ke pencarian dua fase, yang berarti ada dua "akar" yang sepenuhnya terpisah untuk memulai saat melakukan penelusuran. The dasar ide adalah cukup sederhana meskipun: mulai dari lingkup melampirkan terkecil, dan cara kerja Anda ke luar untuk lingkup melampirkan lebih besar dan lebih besar.
Jerry Coffin
sumber
1
Stroustrup berspekulasi di D&E bahwa aturan tersebut mungkin merupakan efek samping dari preprocessor yang digunakan di C dengan Classes yang tidak pernah ditinjau setelah teknologi compiler yang lebih maju tersedia. Lihat jawaban saya .
TemplateRex
12

Kontrol akses ( public, protected, private) tidak mempengaruhi berlebihan resolusi. Kompilator memilih void foo()karena paling cocok. Fakta bahwa itu tidak dapat diakses tidak mengubah itu. Membuangnya hanya menyisakan void foo() const, yang kemudian merupakan yang terbaik (yaitu, hanya) yang cocok.

Pete Becker
sumber
11

Dalam panggilan ini:

a.foo();

Selalu ada thispenunjuk implisit yang tersedia di setiap fungsi anggota. Dan constkualifikasi thisdiambil dari referensi / objek pemanggil. Panggilan di atas diperlakukan oleh kompilator sebagai:

A::foo(a);

Tetapi Anda memiliki dua deklarasi A::fooyang diperlakukan seperti :

A::foo(A* );
A::foo(A const* );

Dengan membebani resolusi, yang pertama akan dipilih untuk non-const this, yang kedua akan dipilih untuk a const this. Jika Anda menghapus yang pertama, yang kedua akan mengikat keduanya constdan non-const this.

Setelah resolusi berlebih untuk memilih fungsi terbaik yang layak, datanglah kontrol akses. Karena Anda menentukan akses ke overload yang dipilih sebagai private, kompilator akan mengeluh.

Standar mengatakan demikian:

[class.access / 4] : ... Dalam kasus nama fungsi yang kelebihan beban, kontrol akses diterapkan ke fungsi yang dipilih oleh resolusi berlebih ....

Tetapi jika Anda melakukan ini:

A a;
const A& ac = a;
ac.foo();

Kemudian, hanya constkelebihan beban yang akan cocok.

WhiZTiM
sumber
Itulah ANEH bahwa Setelah resolusi berlebihan untuk memilih fungsi terbaik yang layak, muncullah kontrol akses . Kontrol akses harus datang sebelum resolusi yang berlebihan seolah-olah Anda tidak memiliki akses jika Anda tidak harus mempertimbangkannya sama sekali, bagaimana menurut Anda?
Narek
@Narek, .. Saya telah memperbarui jawaban saya dengan referensi ke standar C ++. Sebenarnya masuk akal seperti itu, ada banyak hal dan idiom dalam C ++ yang bergantung pada perilaku ini
WhiZTiM
9

Alasan teknis telah dijawab oleh jawaban lain. Saya hanya akan fokus pada pertanyaan ini:

Dengan kata lain mengapa resolusi kelebihan beban harus ada sebelum kontrol akses? Ini aneh. Apakah menurut Anda itu konsisten? Kode saya berfungsi dan kemudian saya menambahkan metode dan kode kerja saya tidak dapat dikompilasi sama sekali.

Begitulah cara bahasa itu dirancang. Maksudnya adalah mencoba memanggil overload yang layak terbaik, sejauh mungkin. Jika gagal, kesalahan akan muncul untuk mengingatkan Anda untuk mempertimbangkan desain lagi.

Di sisi lain, misalkan kode Anda dikompilasi dan bekerja dengan baik dengan constfungsi anggota yang sedang dipanggil. Suatu saat, seseorang (mungkin diri Anda sendiri) kemudian memutuskan untuk mengubah aksesibilitas fungsi non- constanggota dari privatemenjadi public. Kemudian, perilaku akan berubah tanpa kesalahan kompilasi! Ini akan menjadi kejutan .

songyuanyao
sumber
8

Karena variabel adalam mainfungsi tidak dideklarasikan sebagai const.

Fungsi anggota konstan dipanggil pada objek konstan.

Beberapa programmer
sumber
8

Penentu akses tidak pernah memengaruhi pencarian nama dan resolusi panggilan fungsi. Fungsi ini dipilih sebelum kompilator memeriksa apakah panggilan tersebut harus memicu pelanggaran akses.

Dengan cara ini, jika Anda mengubah penentu akses, Anda akan diberi tahu pada waktu kompilasi jika ada pelanggaran dalam kode yang ada; jika privasi diperhitungkan untuk resolusi panggilan fungsi, perilaku program Anda dapat berubah secara diam-diam.

Kyle Strand
sumber