Sanal işlevlerin varsayılan parametreleri olabilir mi?


164

Bir temel sınıf (veya arabirim sınıfı) bildirir ve parametrelerinden biri veya daha fazlası için varsayılan bir değer belirtirseniz, türetilmiş sınıfların aynı varsayılanları belirtmesi gerekir mi, yoksa türetilmiş sınıflarda hangi varsayılanlar gösterilir?

Zeyilname: Bunun farklı derleyiciler arasında nasıl ele alınabileceği ve bu senaryoda "önerilen" uygulamalarla ilgili herhangi bir girdi ile de ilgileniyorum.


1
Test edilmesi kolay bir şey gibi görünüyor. Bunu denediniz mi?
ve

22
Deneme sürecindeyim ama davranışın nasıl tanımlanacağına dair somut bir bilgi bulamadım, bu yüzden sonunda derleyici için bir cevap bulacağım, ancak tüm derleyiciler aynı şeyi yapıp yapmayacağını bana söylemeyecek şey. Ayrıca önerilen uygulamalarla da ilgileniyorum.
Arnold Spence

1
Davranış iyi tanımlanmış ve yanlış anlayan bir derleyici bulacağınızdan şüpheliyim (belki de gcc 1.x veya VC ++ 1.0 ya da bunun gibi bir şeyi test ederseniz). Önerilen uygulama bunu yapmamaktır.
Jerry Coffin

Yanıtlar:


213

Sanalların varsayılan değerleri olabilir. Temel sınıftaki varsayılanlar türetilmiş sınıflar tarafından miras alınmaz.

Hangi varsayılan kullanılır - yani, temel sınıf 'veya türetilmiş bir sınıf' - işleve çağrı yapmak için kullanılan statik tür tarafından belirlenir. Bir temel sınıf nesnesi, işaretçi veya başvuruyu çağırırsanız, temel sınıfta belirtilen varsayılan değer kullanılır. Tersine, türetilmiş bir sınıf nesnesi, işaretçi veya referansı çağırırsanız türetilmiş sınıfta belirtilen varsayılanlar kullanılır. Standart teklifin altında bunu gösteren bir örnek vardır.

Bazı derleyiciler farklı bir şey yapabilir, ancak C ++ 03 ve C ++ 11 Standartlarının söylediği budur:

8.3.6.10:

Sanal işlev çağrısı (10.3), sanal işlevin bildiriminde, işaretçiyi veya nesneyi gösteren başvuruyu statik türüyle belirlenen varsayılan bağımsız değişkenleri kullanır. Türetilmiş bir sınıftaki geçersiz kılma işlevi, geçersiz kıldığı işlevden varsayılan bağımsız değişkenler almaz. Misal:

struct A {
  virtual void f(int a = 7);
};
struct B : public A {
  void f(int a);
};
void m()
{
  B* pb = new B;
  A* pa = pb;
  pa->f(); //OK, calls pa->B::f(7)
  pb->f(); //error: wrong number of arguments for B::f()
}

Hangi varsayılanların alındığını gösteren örnek bir program. structBurada classsadece kısalık için es yerine s kullanıyorum - classve structvarsayılan görünürlük dışında hemen hemen her şekilde aynı.

#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>

using std::stringstream;
using std::string;
using std::cout;
using std::endl;

struct Base { virtual string Speak(int n = 42); };
struct Der : public Base { string Speak(int n = 84); };

string Base::Speak(int n) 
{ 
    stringstream ss;
    ss << "Base " << n;
    return ss.str();
}

string Der::Speak(int n)
{
    stringstream ss;
    ss << "Der " << n;
    return ss.str();
}

int main()
{
    Base b1;
    Der d1;

    Base *pb1 = &b1, *pb2 = &d1;
    Der *pd1 = &d1;
    cout << pb1->Speak() << "\n"    // Base 42
        << pb2->Speak() << "\n"     // Der 42
        << pd1->Speak() << "\n"     // Der 84
        << endl;
}

Bu programın çıktısı (MSVC10 ve GCC 4.4'te):

Base 42
Der 42
Der 84

Referans için teşekkürler, bu bana derleyiciler arasında makul beklediğim davranışı anlatıyor (umarım).
Arnold Spence

Bu benim önceki özetimin bir düzeltmesi: Referans için bu cevabı kabul edeceğim ve toplu tavsiyenin, daha önce bir atada belirtilen varsayılan parametreleri değiştirmedikleri sürece sanal fonksiyonlarda varsayılan parametrelere sahip olmanın uygun olduğunu belirteceğim. sınıf.
Arnold Spence

Gcc 4.8.1 kullanıyorum ve derleme hatası "yanlış sayıda argüman" alamadım !!! Hatayı bulmak için bir buçuk gün
sürdü

2
Ama bunun için bir sebep var mı? Statik tipe göre neden belirlenir?
user1289

2
Clang-tidy, sanal yöntemlerde varsayılan parametreleri istenmeyen bir şey olarak görür ve bu konuda bir uyarı verir: github.com/llvm-mirror/clang-tools-extra/blob/master/clang-tidy/…
Martin Pecka

38

Bu, Herb Sutter'in Haftanın ilk Guru yazılarından birinin konusuydu .

Konuyla ilgili söylediği ilk şey, YAPMAYIN.

Daha ayrıntılı olarak, evet, farklı varsayılan parametreler belirtebilirsiniz. Sanal işlevlerle aynı şekilde çalışmazlar. Sanal parametre, nesnenin dinamik türünde çağrılırken, varsayılan parametre değerleri statik türe dayanır.

verilmiş

class A {
    virtual void foo(int i = 1) { cout << "A::foo" << i << endl; }
};
class B: public A {
    virtual void foo(int i = 2) { cout << "B::foo" << i << endl; }
};
void test() {
A a;
B b;
A* ap = &b;
a.foo();
b.foo();
ap->foo();
}

A :: foo1 B :: foo2 B :: foo1 almalısınız


7
Teşekkürler. Herb Sutter'dan "Bunu yapma" biraz ağırlık taşır.
Arnold Spence

2
@ArnoldSpence, aslında Herb Sutter bu tavsiyenin ötesine geçer. Bir arayüzün sanal yöntemler içermemesi gerektiğine inanıyor: gotw.ca/publications/mill18.htm . Yöntemleriniz somut olduğunda ve geçersiz kılınamazsa (geçersiz kılınmamalıdır), onlara varsayılan parametreler vermek güvenlidir.
Mark Ransom

1
Ne o "yapmayın demek inan o değil, "sanal yöntemlerde varsayılan parametreleri belirtmeyen" yöntemlerini geçersiz "varsayılan parametresinin varsayılan değeri değişmez" idi"
Weipeng L

6

Bu kötü bir fikirdir, çünkü aldığınız varsayılan argümanlar nesnenin statik türüne bağlı olacaktır , ancak virtualgönderilen işlev dinamik türe bağlı olacaktır .

Diğer bir deyişle, varsayılan bağımsız değişkenleri olan bir işlevi çağırdığınızda, varsayılan bağımsız değişkenler, işlevin olup olmadığına bakılmaksızın derleme zamanında değiştirilir virtual.

@cppcoder, [kapalı] sorusunda aşağıdaki örneği sundu :

struct A {
    virtual void display(int i = 5) { std::cout << "Base::" << i << "\n"; }
};
struct B : public A {
    virtual void display(int i = 9) override { std::cout << "Derived::" << i << "\n"; }
};

int main()
{
    A * a = new B();
    a->display();

    A* aa = new A();
    aa->display();

    B* bb = new B();
    bb->display();
}

Hangi aşağıdaki çıktıyı üretir:

Derived::5
Base::5
Derived::9

Yukarıdaki açıklamanın yardımıyla, nedenini görmek kolaydır. Derleme zamanında derleyici, statik işaretçi türlerinin üye işlevlerindeki varsayılan bağımsız değişkenlerin yerine geçer ve mainişlevi aşağıdakilere eşdeğer yapar:

    A * a = new B();
    a->display(5);

    A* aa = new A();
    aa->display(5);

    B* bb = new B();
    bb->display(9);

4

Diğer cevaplardan da görebileceğiniz gibi, bu karmaşık bir konudur. Bunu yapmaya çalışmak ya da ne yaptığını anlamak yerine (eğer şimdi sormak zorunda kalırsanız, bakıcı bundan bir yıl sonra sormak ya da aramak zorunda kalacak).

Bunun yerine, temel sınıfta varsayılan parametrelerle ortak bir sanal olmayan işlev oluşturun. Ardından, varsayılan parametreleri olmayan ve gerektiğinde alt sınıflarda geçersiz kılınan özel veya korumalı bir sanal işlevi çağırır. O zaman nasıl çalışacağının ayrıntıları hakkında endişelenmenize gerek yok ve kod çok açık.


1
Hiç de karmaşık değil. Varsayılan parametreler ad çözümlemesiyle birlikte bulunur. Aynı kurallara uyarlar.
Edward Strange

4

Bu muhtemelen test ederek makul derecede iyi anlayabileceğiniz bir şeydir (yani, çoğu derleyicinin neredeyse kesinlikle doğru bulduğu dilin yeterince ana akım bir parçasıdır ve derleyiciler arasında farklılıklar görmedikçe, çıktıları oldukça iyi bir şekilde kabul edilebilir).

#include <iostream>

struct base { 
    virtual void x(int a=0) { std::cout << a; }
    virtual ~base() {}
};

struct derived1 : base { 
    void x(int a) { std:: cout << a; }
};

struct derived2 : base { 
    void x(int a = 1) { std::cout << a; }
};

int main() { 
    base *b[3];
    b[0] = new base;
    b[1] = new derived1;
    b[2] = new derived2;

    for (int i=0; i<3; i++) {
        b[i]->x();
        delete b[i];
    }

    derived1 d;
    // d.x();       // won't compile.
    derived2 d2;
    d2.x();
    return 0;
}

4
@GMan: [Dikkatle masum görünüyor] Ne sızıyor? :-)
Jerry Coffin

Bence sanal bir yıkıcı eksikliğinden bahsediyor. Ancak bu durumda sızmaz.
John Dibling

1
@Jerry, temel sınıf işaretçisi üzerinden türetilmiş nesneyi siliyorsanız yıkıcı sanal olmuştur. Aksi takdirde hepsi için temel sınıf yıkıcısı çağrılır. Bu, yıkıcı olmadığı için sorun değil. :-)
chappar

2
@John: Aslında hiç silme yoktu, bahsettiğim şey buydu. Sanal bir yıkıcı eksikliğini tamamen görmezden geldim. Ve ... @chappar: Hayır, sorun değil. Bu gerekir sanal yıkıcı bir temel sınıf yoluyla Silinecek varsa, veya tanımsız davranış olsun. (Bu kod tanımsız bir davranışa sahiptir.) Türetilmiş sınıfların sahip olduğu veri veya yıkıcılarla hiçbir ilgisi yoktur.
GManNickG

@Chappar: Kod başlangıçta hiçbir şeyi silmedi. Eldeki soru ile çoğunlukla alakasız olsa da, temel sınıfa sanal bir dtor ekledim - önemsiz bir dtor ile nadiren önemli, ancak GMan, onsuz, kodun UB'ye sahip olduğunu tamamen doğru.
Jerry Coffin

4

Diğer cevapların detaylandırdığı gibi, kötü fikir. Ancak hiç kimse basit ve etkili bir çözümden bahsetmediği için, burada: Parametrelerinizi yapıya dönüştürün ve sonra yapı üyelerine varsayılan değerlere sahip olabilirsiniz!

Yani yerine,

//bad idea
virtual method1(int x = 0, int y = 0, int z = 0)

Bunu yap,

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)
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.