Const olmayan bir özel olduğunda neden bir public const yöntemi çağrılmaz?


117

Bu kodu düşünün:

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();
}

Derleyici hatası:

hata: 'void A :: foo ()' özeldir`.

Ama özel olanı sildiğimde sadece çalışıyor. Const olmayan yöntem özel olduğunda neden public const yöntemi çağrılmıyor?

Başka bir deyişle, aşırı yük çözümü neden erişim kontrolünden önce geliyor? Bu tuhaf. Tutarlı olduğunu düşünüyor musunuz? Kodum çalışıyor ve sonra bir yöntem ekliyorum ve çalışma kodum hiç derlenmiyor.


3
C ++ 'da, PIMPL deyimini kullanmak gibi ekstra çaba sarf etmeden, sınıfın gerçek bir "özel" kısmı yoktur. Bu, ("özel" bir yöntem aşırı yüklenmesi ve derleme eski kodunun kırılması kitabımda bir sorun olarak sayılır, bunu yapmamakla kaçınılması önemsiz olsa bile) sorunlardan sadece biridir.
hyde

Bir const işlevini çağırabilmeyi beklediğiniz, ancak bu işlevin const olmayan karşılığının özel arabirimin bir parçası olacağı bir gerçek hayat kodu var mı? Bu bana kötü arayüz tasarımı gibi geliyor.
Vincent Fourmond

Yanıtlar:


125

Aradığınızda a.foo();, derleyici kullanılacak en iyi işlevi bulmak için aşırı yük çözümlemesinden geçer. Aşırı yük setini oluşturduğunda bulur

void foo() const

ve

void foo()

Şimdi, aolmadığı için const, const olmayan sürüm en iyi eşleşmedir, bu nedenle derleyici seçer void foo(). Daha sonra erişim kısıtlamaları devreye girer ve void foo()özel olduğu için derleyici hatası alırsınız .

Unutmayın, aşırı yük çözümlemede "en iyi kullanılabilir işlevi bulma" değildir. "En iyi işlevi bulun ve kullanmaya çalışın" dır. Erişim kısıtlamaları veya silinmesi nedeniyle yapılamazsa, bir derleyici hatası alırsınız.

Başka bir deyişle, aşırı yük çözümü neden erişim kontrolünden önce gelir?

Peki, bakalım:

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);
}

Şimdi diyelim ki aslında void foo(Derived * d)özel yapmak istemedim . Erişim kontrolü önce gelirse, bu program derlenir, çalışır ve Baseyazdırılır. Bunu büyük bir kod tabanında bulmak çok zor olabilir. Erişim kontrolü, aşırı yük çözümlemesinden sonra geldiğinden, çağırmasını istediğim işlevin çağrılamayacağını söyleyen güzel bir derleyici hatası alıyorum ve hatayı çok daha kolay bulabilirim.


Erişim kontrolünün aşırı yük çözümlemesinden sonra olmasının herhangi bir nedeni var mı?
drake7707

3
@ drake7707 Kod örneğimde gösterdiğim gibi size erişim kontrolü önce gelirse, yukarıdaki kod derlenir, bu da programın anlamını değiştirir. Senden emin değilim, ancak bir hata yapmayı tercih ederim ve işlevin özel kalmasını istersem, örtük bir döküm ve kod sessizce "çalışır".
NathanOliver

"ve eğer işlevin özel kalmasını istiyorsam açık bir atama yapmam gerekiyor" - buradaki asıl mesele örtük yayınlar gibi görünüyor ... öte yandan, türetilmiş bir sınıfı örtük olarak da kullanabileceğiniz fikri temel sınıf, OO paradigmasının tanımlayıcı bir özelliğidir, değil mi?
Steven Byks

35

Nihayetinde bu, standartta, aşırı yük çözümü gerçekleştirilirken erişilebilirliğin dikkate alınmaması gerektiği iddiasına iner . Bu iddia [over.match] 3. maddede bulunabilir :

... Aşırı yük çözümü başarılı olduğunda ve kullanılabilir en iyi işlev kullanıldığı bağlamda erişilebilir olmadığında (Madde [class.access]), program biçimsizdir.

ve aynı bölümün 1. fıkrasında yer alan Not :

[Not: Aşırı yük çözünürlüğü tarafından seçilen işlevin bağlama uygun olduğu garanti edilmez. İşlevin erişilebilirliği gibi diğer kısıtlamalar, işlevin çağrı bağlamında kötü biçimlendirilmiş şekilde kullanılmasına neden olabilir. - son not]

Nedenine gelince, birkaç olası motivasyon düşünebilirim:

  1. Bir aşırı yük adayının erişilebilirliğini değiştirmenin bir sonucu olarak beklenmedik davranış değişikliklerini önler (bunun yerine, bir derleme hatası oluşacaktır).
  2. Bağlam bağımlılığını aşırı yük çözümleme sürecinden kaldırır (yani aşırı yük çözümlemesi, sınıf içinde veya dışında aynı sonuca sahip olacaktır).

32

Erişim kontrolünün aşırı yük çözümünden önce geldiğini varsayalım. Etkili olarak, bu public/protected/privateerişilebilirlikten ziyade kontrollü görünürlük anlamına gelir .

Stroustrup tarafından yazılan C ++ Tasarım ve Evrimi Bölüm 2.10 , aşağıdaki örneği tartıştığı bir bölüme sahiptir.

int a; // global a

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

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

Stroustrup Geçerli kurallara (erişilebilirlik önce görünürlük) yararlarından (geçici olarak) chaning olmasıdır bahseder privateiçini class Xiçine public(örneğin hata ayıklama amacıyla) Yukarıdaki programın anlamında hiçbir sessiz değişiklik (yani olmasıdır X::açalışılır her iki durumda da erişilebilir, bu da yukarıdaki örnekte bir erişim hatası verir). Eğer public/protected/privategörünürlüğünü kontrol ediyorum, değiştirecek programın anlamı (küresel aile aranmak private, aksi X::a).

Daha sonra bunun açık bir tasarımdan mı yoksa Standard C ++ 'ya Classess selefi ile C'yi uygulamak için kullanılan önişlemci teknolojisinin bir yan etkisinden mi kaynaklandığını hatırlamadığını belirtir.

Bunun örneğinizle nasıl bir ilişkisi var? Temel olarak, Standart yapılan aşırı yük çözümlemesi, ad aramasının erişim kontrolünden önce geldiği genel kurala uyduğundan.

10.2 Üye adı araması [class.member.lookup]

1 Üye adı araması, bir sınıf kapsamındaki (3.3.7) bir adın (id-ifadesi) anlamını belirler. Ad araması bir belirsizliğe neden olabilir ve bu durumda program kötü biçimlendirilir. Bir id ifadesi için ad araması bunun sınıf kapsamında başlar; nitelenmiş kimlik için ad araması, iç içe geçmiş ad tanımlayıcısının kapsamında başlar. Ad araması, erişim kontrolünden önce gerçekleşir (3.4, Madde 11).

8 Aşırı yüklenmiş bir işlevin adı açık bir şekilde bulunursa, aşırı yükleme çözünürlüğü (13.3) de erişim kontrolünden önce gerçekleşir . Belirsizlikler genellikle bir adı sınıf adıyla nitelendirerek çözülebilir.


23

Örtük thisgösterici non- constolduğundan, derleyici constönce bir constsürümden önce işlevin sürümü olmayan bir işlevin varlığını kontrol edecektir .

Açıkça olmayan işaretlerseniz constbirini privatedaha sonra çözünürlüğü başarısız olur ve derleyici Aramaya devam olmayacaktır.


Tutarlı olduğunu düşünüyor musunuz? Kodum çalışıyor ve sonra bir yöntem ekliyorum ve çalışma kodum hiç derlenmiyor.
Narek

Bende öyle düşünüyorum. Aşırı yük çözünürlüğü kasıtlı olarak telaşlıdır. Dün de benzer bir soruyu yanıtladım: stackoverflow.com/questions/39023325/…
Bathsheba

5
@Narek Aşırı yük çözümlemede silinen işlevlerin yaptığı gibi çalıştığına inanıyorum. Setten en iyi olanı seçer ve daha sonra mevcut olmadığını görür, böylece bir derleyici hatası alırsınız. Kullanılabilir en iyi işlevi değil, en iyi işlevi seçer ve sonra onu kullanmaya çalışır.
NathanOliver

3
@Narek İlk olarak neden çalışmadığını da merak ettim, ama şunu bir düşünün: eğer public const aynı zamanda const olmayan nesneler için de seçilecekse özel işlevi nasıl çağırırsınız?
idclev 463035818

20

Olan şeylerin sırasını akılda tutmak önemlidir, yani:

  1. Uygulanabilir tüm işlevleri bulun.
  2. Uygulanabilir en iyi işlevi seçin.
  3. Uygulanabilir en iyi bir tam olarak yoksa veya gerçekten uygulanabilir en iyi işlevi arayamazsanız (erişim ihlalleri veya deleted işlevi nedeniyle ), başarısız olun.

(3), (2) 'den sonra olur. Bu gerçekten önemli, çünkü aksi takdirde deleted veya fonksiyonlarını yapmak privateanlamsız ve akıl yürütmesi çok daha zor hale gelir.

Bu durumda:

  1. Uygulanabilir işlevler A::foo()ve A::foo() const.
  2. Uygulanabilir en iyi işlev, A::foo()ikincisinin örtük thisargüman üzerinde bir nitelik dönüşümü içermesidir .
  3. Ama A::foo()olan privateve ona erişiminiz, dolayısıyla kod kötü oluşur yok.

1
"Uygulanabilir" in ilgili erişim kısıtlamalarını içereceği düşünülebilir. Başka bir deyişle, o sınıfın genel arayüzünün bir parçası olmadığı için, sınıfın dışından bir özel işlevi çağırmak "uygulanabilir" değildir.
RM

15

Bu, C ++ 'da oldukça basit bir tasarım kararına bağlıdır.

Bir aramayı tatmin etmek için işlevi ararken, derleyici aşağıdaki gibi bir arama yapar:

  1. Bu isimde bir şeyin olduğu ilk 1 kapsamı bulmak için arama yapar .

  2. Derleyici , bu kapsamda o ada sahip tüm işlevleri (veya işlevcileri, vb.) Bulur.

  3. Daha sonra derleyici, buldukları arasından en iyi adayı bulmak için (erişilebilir olsun veya olmasın) aşırı yükleme çözümü yapar.

  4. Son olarak, derleyici seçilen işleve erişilebilir olup olmadığını kontrol eder.

Bu sıralama nedeniyle, evet, erişilebilir başka bir aşırı yük olmasına rağmen (ancak aşırı yük çözümlemesi sırasında seçilmemiş) derleyicinin erişilebilir olmayan bir aşırı yüklemeyi seçmesi mümkündür.

İşleri farklı şekilde yapmanın mümkün olup olmayacağına gelince : evet, şüphesiz mümkün. Yine de kesinlikle C ++ 'dan oldukça farklı bir dile yol açacaktır. Görünüşe göre önemsiz görünen pek çok kararın başlangıçta açık olandan çok daha fazlasını etkileyen sonuçları olabileceği ortaya çıktı.


  1. "İlk", kendi içinde biraz karmaşık olabilir, özellikle şablonlar dahil olduğunda / olursa, iki aşamalı aramaya yol açabilirler, yani arama yaparken başlamak için tamamen ayrı iki "kök" vardır. Yine de temel fikir oldukça basit: en küçük kapsama alanından başlayın ve daha büyük ve daha büyük kapsama alanlarına doğru ilerleyin.

1
Stroustrup, D & E'de kuralın, gelişmiş derleyici teknolojisi bir kez daha gözden geçirilmemiş olan Sınıflar ile C'de kullanılan önişlemcinin bir yan etkisi olabileceğini tahmin ediyor. Cevabımı gör .
TemplateRex

12

Erişim kontrolleri ( public, protected, private) çözünürlüğü aşırı etkilemez. Derleyici void foo(), en iyi eşleşme olduğu için seçer . Erişilebilir olmaması bunu değiştirmez. Kaldırıldığında yalnızca kalır void foo() const, o zaman en iyi (yani yalnızca) eşleşme olur.


11

Bu görüşmede:

a.foo();

thisHer üye işlevinde her zaman örtük bir işaretçi vardır. Ve constniteliği thisçağıran referanstan / nesneden alınır. Yukarıdaki çağrı , derleyici tarafından şu şekilde ele alınır :

A::foo(a);

Ancak A::fooşu şekilde muamele gören iki beyanınız var :

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

Aşırı yük çözümlemesiyle, birincisi const olmayan için seçilecek this, ikincisi ise a için seçilecektir const this. Birinciyi kaldırırsanız, ikincisi hem constve hem de non-const this.

En uygun işlevi seçmek için aşırı yük çözünürlüğünden sonra erişim kontrolü gelir. Seçilen aşırı yüklemeye erişimi olarak belirttiğinizden private, derleyici bundan sonra şikayet edecektir.

Standart şöyle diyor:

[class.access / 4] : ... Aşırı yüklenmiş fonksiyon isimleri durumunda, aşırı yük çözünürlüğü ile seçilen fonksiyona erişim kontrolü uygulanır ....

Ama bunu yaparsan:

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

O zaman sadece constaşırı yük uygun olacaktır.


Bu, uygulanabilir en iyi işlevi seçmek için aşırı yük çözünürlüğünden sonra erişim kontrolünün gelmesidir . Erişim kontrolü, sanki erişiminiz yokmuş gibi aşırı yük çözümünden önce gelmelidir, hiç düşünmemelisiniz, ne düşünüyorsunuz?
Narek

@Narek, .. Cevabımı C ++ standardına referansla güncelledim . Aslında bu şekilde mantıklı geliyor, C ++ 'da bu davranışa bağlı olan birçok şey ve deyim var
WhiZTiM

9

Teknik neden başka cevaplarla cevaplandı. Sadece bu soruya odaklanacağım:

Başka bir deyişle, aşırı yük çözümü neden erişim kontrolünden önce gelir? Bu tuhaf. Tutarlı olduğunu düşünüyor musunuz? Kodum çalışıyor ve sonra bir yöntem ekliyorum ve çalışma kodum hiç derlenmiyor.

Dil böyle tasarlandı. Amaç, mümkün olduğu kadar uygulanabilir en iyi aşırı yüklemeyi aramaya çalışmaktır. Başarısız olursa, tasarımı tekrar değerlendirmenizi hatırlatan bir hata tetiklenecektir.

Öte yandan, kodunuzun derlendiğini ve constçağrılan üye işleviyle iyi çalıştığını varsayalım . Bir gün, birisi (belki kendin) daha sonra olmayan erişilebilirliğini değiştirmeye karar constden üye işlev privateiçin public. Daha sonra, herhangi bir derleme hatası olmadan davranış değişir! Bu bir sürpriz olur .


8

Değişken Çünkü aiçinde mainfonksiyon olarak bildirilmedi const.

Sabit üye fonksiyonları, sabit nesnelerde çağrılır.


8

Erişim belirteçleri, isim arama ve işlev çağrısı çözümlemesini hiçbir zaman etkilemez. İşlev, derleyici çağrının bir erişim ihlalini tetikleyip tetiklemeyeceğini kontrol etmeden önce seçilir.

Bu şekilde, bir erişim belirtecini değiştirirseniz, derleme sırasında mevcut kodda bir ihlal olup olmadığı konusunda uyarılırsınız; İşlev çağrısı çözümlemesi için gizlilik hesaba katılırsa, programınızın davranışı sessizce değişebilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.