C ++ 'da ne zaman sanal yöntem bildiriminde final kullanmalıyım?


11

finalAnahtar kelimenin sanal yöntemin türetilmiş sınıflar tarafından geçersiz kılınmasını önlemek için kullanıldığını biliyorum . Ancak, yöntemle anahtar kelimeyi gerçekten kullanmam gerektiğinde yararlı bir örnek bulamıyorum . Dahası, sanal yöntemlerle kullanılması kötü bir koku gibi geliyor , çünkü programcıları gelecekte sınıfı genişletmeye izin vermiyor.finalvirtualfinal

Sorum şu:

Gerçekten kullanması gereken herhangi kullanışlı durumda var mıdır finaliçinde virtualyöntem bildiriminde?


Tüm aynı nedenlerden dolayı Java yöntemleri sonlandırılıyor mu?
Sıradan

Java'da tüm yöntemler sanaldır. Temel sınıftaki son yöntem performansı artırabilir. C ++ sanal olmayan yöntemlere sahiptir, bu nedenle bu dil için bir durum değildir. Başka iyi durumlar biliyor musunuz? Onları duymak isterim. :)
metamaker

Sanırım bazı şeyleri açıklama konusunda çok iyi değilim - Mike Nakis ile aynı şeyi söylemeye çalışıyordum.
Sıradan

Yanıtlar:


12

finalAnahtar kelimenin yaptıklarının bir özeti : Diyelim ki temel sınıfımız Ave türetilmiş sınıfımız var B. İşlev f(), Aolarak tanımlanabilir virtual, yani sınıf Bonu geçersiz kılabilir. Ancak o zaman sınıf B, bundan türetilmiş herhangi bir sınıfın Bgeçersiz kılınmamasını isteyebilir f(). Diye ilan gerektiğinde budur f()olarak finaliçinde B. finalAnahtar kelime olmadan, bir yöntemi sanal olarak tanımladığımızda, türetilmiş herhangi bir sınıf bunu geçersiz kılmaktan özgür olacaktır. finalAnahtar kelime, bu özgürlüğe bir son vermek için kullanılır.

Eğer böyle bir şeye ihtiyacım nedenlerine ilişkin bir örnek: varsayalım sınıf Asanal işlevi tanımlar prepareEnvelope()ve sınıf Bkendi sanal yöntemlere aramaların bir dizi olarak geçersiz kılar onu ve uygular onu stuffEnvelope(), lickEnvelope()ve sealEnvelope(). Sınıf B, türetilmiş sınıfların kendi sanal uygulamalarını sağlamak için bu sanal yöntemleri geçersiz kılmalarına izin verir, ancak sınıf B, türetilmiş herhangi bir sınıfın geçersiz kılınmasına izin vermez prepareEnvelope()ve böylece bunlardan birini çağırmak için kullanılan öğelerin, yalama, mühürleme veya atlamanın sırasını değiştirmez. Yani, bu durumda sınıf nihai olarak Bilan prepareEnvelope()edilir.


İlk olarak, cevap için teşekkür ederim ama sorumun ilk cümlesinde yazdıklarım tam olarak bu değil mi? Gerçekten ihtiyacım olan şey ne zaman class B might wish that any class which is further derived from B should not be able to override f()? B sınıfı ve yöntem f () 'nin var olduğu ve iyi uyduğu gerçek bir dünya durumu var mı?
metamaker

Evet, üzgünüm, bekle, şu anda bir örnek yazıyorum.
Mike Nakis

@metamaker işte gidiyorsunuz.
Mike Nakis

1
Gerçekten iyi bir örnek, bu şimdiye kadarki en iyi cevap. Teşekkür!
metamaker

1
O zaman boşa harcadığınız için teşekkürler. İnternette iyi bir örnek bulamadım, çok fazla zaman harcadım. Cevap için +1 koyardım ama itibarım düşük olduğu için yapamam. :( Belki sonra.
metamaker

2

Tasarım perspektifinden işleri değişmez olarak işaretlemek genellikle yararlıdır. Aynı şekilde, constderleyici koruma sağlar ve bir durumun değişmemesi gerektiğini finalbelirtir, davranışın miras hiyerarşisinde daha fazla değişmemesi gerektiğini belirtmek için kullanılabilir.

Misal

Araçların oynatıcıyı bir yerden başka bir yere götürdüğü bir video oyunu düşünün. Tüm araçlar kalkıştan önce geçerli bir yere seyahat ettiklerini kontrol etmelidir (örneğin, yerdeki tabanın tahrip olmadığından emin olun). Bu kontrolün araçtan bağımsız olarak yapıldığından emin olmak için sanal olmayan arayüz deyimini (NVI) kullanmaya başlayabiliriz.

class Vehicle
{
public:
    virtual ~Vehicle {}

    bool transport(const Location& location)
    {
        // Mandatory check performed for all vehicle types. We could potentially
        // throw or assert here instead of returning true/false depending on the
        // exceptional level of the behavior (whether it is a truly exceptional
        // control flow resulting from external input errors or whether it's
        // simply a bug for the assert approach).
        if (valid_location(location))
            return travel_to(location);

        // If the location is not valid, no vehicle type can go there.
        return false;
    }

private:
    // Overridden by vehicle types. Note that private access here
    // does not prevent derived, nonfriends from being able to override
    // this function.
    virtual bool travel_to(const Location& location) = 0;
};

Şimdi diyelim ki oyunumuzda uçan araçlarımız var ve tüm uçan araçların gerektirdiği ve ortak noktası olan şey, kalkıştan önce hangarın içinde bir güvenlik denetimi kontrolünden geçmeleri gerektiğidir.

Burada final, tüm uçan araçların böyle bir incelemeden geçeceğini ve aynı zamanda uçan araçların bu tasarım gereksinimini ileteceğini garanti etmek için kullanabiliriz .

class FlyingVehicle: public Vehicle
{
private:
    bool travel_to(const Location& location) final
    {
        // Mandatory check performed for all flying vehicle types.
        if (safety_inspection())
            return fly_to(location);

        // If the safety inspection fails for a flying vehicle, 
        // it will not be allowed to fly to the location.
        return false;
    }

    // Overridden by flying vehicle types.
    virtual void safety_inspection() const = 0;
    virtual void fly_to(const Location& location) = 0;
};

finalBu şekilde kullanarak , sanal olmayan arayüz deyiminin esnekliğini, miras hiyerarşisinde (sonradan düşünüldüğünde bile, kırılgan temel sınıf sorununa karşı koyarken) sanal işlevlerin kendilerine eşit bir davranış sağlamak için genişletiyoruz. Ayrıca, var olan her uçan araç uygulamasını değiştirmeden, tüm uçan araç türlerini etkileyen bir sonraki düşünce olarak merkezi değişiklikler yapmak için kendimizi kıpırdatmak için oda satın alıyoruz.

Bu, bunun gibi bir örnek final. Sanal üye işlevinin daha fazla geçersiz kılınmasının mantıklı olmadığı yerlerde karşılaşacağınız bağlamlar vardır - bunu yapmak kırılgan bir tasarıma ve tasarım gereksinimlerinizin ihlaline yol açabilir.

finalTasarım / mimari açıdan faydalı olan yer burasıdır .

Optimize ediciye, sanal işlev çağrılarını (dinamik dağıtım yükünü ortadan kaldırarak ve genellikle daha da önemlisi, arayan ve arayan arasında bir optimizasyon engelini ortadan kaldırarak) devretmesini sağlayan bu tasarım bilgilerini sağladığından, bir optimize edicinin bakış açısından da yararlıdır.

Soru

Yorumlardan:

Nihai ve sanal neden aynı anda kullanılsın?

Hiyerarşinin kökündeki bir temel sınıfın bir işlevi hem virtualve hem de olarak tanımlaması mantıklı değildir final. Hem derleyici hem de insan okuyucunun virtual, böyle bir durumda basitçe kaçınarak kaçınılabilen gereksiz halkalardan atlamak zorunda kalacağı için bu bana aptalca geliyor . Ancak, alt sınıflar aşağıdaki gibi sanal üye işlevlerini devralır:

struct Foo
{
   virtual ~Foo() {}
   virtual void f() = 0;
};

struct Bar: Foo
{
   /*implicitly virtual*/ void f() final {...}
};

Bu durumda, Bar::façıkça sanal anahtar sözcüğü kullanıp kullanmadığı Bar::fsanal bir işlevdir. virtualAnahtar kelime daha sonra bu durumda isteğe bağlı hale gelir. İçin mantıklı olabilir Yani Bar::folarak belirtilmesi finalbunun sanal bir fonksiyonu olsa, ( finalolabilir sadece sanal fonksiyonlar için kullanılabilir).

Ve bazı insanlar, stilistik olarak, Bar::fbunun sanal olduğunu açıkça belirtmeyi tercih edebilir :

struct Bar: Foo
{
   virtual void f() final {...}
};

Bana göre bu tür her ikisini de kullanmak yedekli ait olduğunu virtualve finalbu bağlamda (aynı şekilde aynı işlev için belirteçleri virtualve override), ancak bu durumda tarzı meselesi. Bazı insanlar , dış bağlantı ile fonksiyon bildirimleri virtualkullanmak gibi (burada externdiğer bağlantı niteleyicilerinden yoksun olsa da) değerli bir şeyi ilettiğini fark edebilir .


Sınıfınızdaki privateyöntemi nasıl geçersiz kılmayı planlıyorsunuz Vehicle? Bunu mu demek istediniz protected?
Andy

3
@DavidPacker privategeçersiz kılmaya (biraz karşı sezgisel) uzanmıyor . Sanal işlevler için genel / korumalı / özel belirteçler, yalnızca arayanlar için geçerlidir ve geçersiz kılanlar için geçerli değildir. Türetilmiş bir sınıf, görünürlüğünden bağımsız olarak sanal işlevleri temel sınıfından geçersiz kılabilir.

@DavidPacker protectedbiraz daha sezgisel olabilir. Sadece elimden geldiğince mümkün olan en düşük görüş açısına ulaşmayı tercih ediyorum. Dilin bu şekilde tasarlanmasının sebebi, aksi takdirde, özel sanal üye işlevlerinin dostluk bağlamının dışında hiçbir anlam ifade etmemesidir, çünkü sınıftan başka bir sınıftan başka bir arkadaş bu bağlamı geçersiz kılabilir. sadece çağırmakla kalmayıp,

Özel olarak ayarlanmış olması bile derlenmeyeceğini düşündüm. Örneğin, ne Java ne de C # buna izin vermez. C ++ beni her gün şaşırtıyor. 10 yıllık programlamadan sonra bile.
Andy

@DavidPacker İlk karşılaştığımda beni de tetikledi. Belki de protectedbaşkalarını karıştırmamak için yapmalıyım . En azından özel sanal işlevlerin hala nasıl geçersiz kılınabileceğini açıklayan bir yorum yaptım.

0
  1. Çok sayıda optimizasyon sağlar, çünkü derleme zamanında hangi fonksiyonun adı verildiği bilinebilir.

  2. Etrafında "kod kokusu" kelimesini atmaya dikkat edin. "final", sınıfı genişletmeyi imkansız kılmaz. "Son" kelimesine çift tıklayın, geri tuşuna basın ve sınıfı genişletin. HOWEVER final, geliştiricinin işlevi geçersiz kılmanızı beklemediği ve bundan dolayı bir sonraki geliştiricinin çok dikkatli olması gerektiği için mükemmel bir belgedir, çünkü son yöntem bu şekilde geçersiz kılındığında sınıf düzgün çalışmayı durdurabilir.


Neden olur finalve virtualhiç aynı anda kullanılabilir?
Robert Harvey

1
Çok sayıda optimizasyon sağlar, çünkü derleme zamanında hangi fonksiyonun adı verildiği bilinebilir. Nasıl referans verebilir veya referans verebilir misiniz? HOWEVER final, geliştiricinin işlevi geçersiz kılmanızı beklemediği ve bundan dolayı bir sonraki geliştiricinin çok dikkatli olması gerektiği için mükemmel bir belgedir, çünkü son yöntem bu şekilde geçersiz kılındığında sınıf düzgün çalışmayı durdurabilir. Kötü bir koku değil mi?
metamaker

@RobertHarvey ABI kararlılığı. Başka bir durumda, muhtemelen finalve olmalıdır override.
Tekilleştirici

@metamaker Ben burada yorumlarda tartışılan aynı Q vardı - programmers.stackexchange.com/questions/256778/… - & funnily sadece soran beri başka tartışmalara tökezledi! Temel olarak, bir optimizasyon derleyicisinin / bağlayıcısının bir referansın / işaretçinin türetilmiş bir sınıfı temsil edip edemeyeceğini ve dolayısıyla sanal bir çağrıya ihtiyacı olup olmadığını belirleyip belirleyemeyeceği ile ilgilidir. Yöntem (veya sınıf), başvurunun kendi statik türünün ötesinde geçersiz kılınamazsa, devirtualize edebilirler: doğrudan çağrı. Herhangi bir şüphe varsa - sık sık - neredeyse aramaları gerekir. Beyan finalgerçekten onlara yardım edebilir
underscore_d
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.