2013-01-16 8 views
11

Wstęp:C++ 11 Usuń przesłonięta metoda

To jest pytanie o najlepsze praktyki dotyczące nowego znaczenia usuwania operatora wprowadzonego z C++ 11, gdy stosowane do klasy dziecięcej nadrzędnymi dziedziczną rodziców wirtualny metoda.

Tło:

Per standardu, pierwszy przypadek użycia cytowany jest wyraźnie zabronić funkcji połączeń dla niektórych rodzajów gdzie konwersje w przeciwnym razie być dorozumiane, takich jak przykładowo z §8.4.3 na ostatni C++11 standard draft:

struct sometype { 
    sometype() = delete; // OK, but redundant 
    some_type(std::intmax_t) = delete; 
    some_type(double); 
}; 

powyższy przykład jest jasne i celowe. Jednak w poniższym przykładzie, w którym nowy operator został nadpisany i uniemożliwiony został wywołany, definiując go jako usunięty, zacząłem myśleć o innych scenariuszach, które później zidentyfikowałem w sekcji pytania (przykład poniżej jest z §8.4.3 z C++11 standard draft):

struct sometype { 
    void *operator new(std::size_t) = delete; 
    void *operator new[](std::size_t) = delete; 
}; 
sometype *p = new sometype; // error, deleted class operator new 
sometype *q = new sometype[3]; // error, deleted class operator new[] 

Pytanie:

Przez rozszerzenie tej myśli do dziedziczenia, jestem ciekaw innych przemyśleń dotyczących tego, czy Poniższy przykład użycie jest jasne i ważne use-case lub jeśli jest to jasne nadużycie nowo dodanej funkcji. Proszę uzasadnić swoją odpowiedź (zostanie przyjęty przykład zapewniający najbardziej przekonujące uzasadnienie). W poniższym przykładzie projekt próbuje utrzymać dwie wersje biblioteki (wymagana jest instancja biblioteki), ponieważ druga wersja biblioteki dziedziczy po raz pierwszy. Chodzi o to, aby poprawki błędów lub zmiany wprowadzone w pierwszej wersji biblioteki były automatycznie propagowane do drugiej wersji biblioteki, pozwalając drugiej wersji biblioteki skupić się tylko na jej różnicach od pierwszej wersji. Deprecate funkcję w drugiej wersji biblioteki, operator delete jest używany, aby uniemożliwić połączenie do zastąpionej funkcję:

class LibraryVersion1 { 
public: 
    virtual void doSomething1() { /* does something */ } 
    // many other library methods 
    virtual void doSomethingN() { /* does something else */ } 
}; 

class LibraryVersion2 : public LibraryVersion1 { 
public: 
    // Deprecate the doSomething1 method by disallowing it from being called 
    virtual void doSomething1() override = delete; 

    // Add new method definitions 
    virtual void doSomethingElse() { /* does something else */ } 
}; 

Choć widzę wiele korzyści takiego podejścia, myślę, że wydają się bardziej ku myśli że jest to nadużycie tej funkcji. Podstawową pułapką, jaką widzę w powyższym przykładzie, jest to, że klasyczna relacja dziedziczenia "jest-a" jest zerwana. Przeczytałem wiele artykułów, które zdecydowanie zalecają przeciwdziałanie dziedziczeniu w celu wyrażenia relacji "sort-of-a-a", a zamiast tego użycie kompozycji z funkcjami wrappera, aby wyraźnie zidentyfikować relacje klas. Podczas gdy poniższy często marszczący się przykład wymaga więcej wysiłku w celu wdrożenia i utrzymania (w odniesieniu do liczby linii napisanych dla tego fragmentu kodu, ponieważ każda dziedziczna funkcja, która ma być publicznie dostępna musi być jawnie wywołana przez klasę dziedziczącą), użycie od delete jak przedstawiono powyżej, jest bardzo podobny pod wieloma względami:

class LibraryVersion1 { 
public: 
    virtual void doSomething1() { /* does something */ } 
    virtual void doSomething2() { /* does something */ } 
    // many other library methods 
    virtual void doSomethingN() { /* does something */ } 
}; 

class LibraryVersion2 : private LibraryVersion1 { 
    // doSomething1 is inherited privately so other classes cannot call it 
public: 
    // Explicitly state which functions have not been deprecated 
    using LibraryVersion1::doSomething2(); 
    // ... using (many other library methods) 
    using LibraryVersion1::doSomethingN(); 

    // Add new method definitions 
    virtual void doSomethingElse() { /* does something else */ } 
}; 

z góry dziękuję za odpowiedzi i dalszego wglądu do tego potencjalnego użytkowej przypadku kasowania.

+0

Nie uważam, że 'virtual void doSomething1() override = delete;' jest legalne. Co byś miał '((LibraryVersion1 *) (new LibraryVersion2)) -> doSomething1()' zrobić? –

+0

Według mojego zrozumienia, nawet z rzutowaniem na LibraryVersion1, usunięta funkcja wciąż próbowałaby zostać wywołana, biorąc pod uwagę zastąpienie przez LibraryVersion2 i spowodować, że kod się nie skompilował. W tym miejscu relacja "jest-a" jest zepsuta, jak zauważono w moim pytaniu, ale z pewnością wymusiłaby deprecjację zgodnie z przeznaczeniem. – statueuphemism

+0

fresh: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf – qPCR4vir

Odpowiedz

5

Paragraf 8.4.3/2 C++ standard pośrednio zabrania usuwania funkcji które zastępują funkcję wirtualnego:

„Program, który odnosi do usuniętej funkcji domyślnie lub jawnie, inne niż zadeklarowanie jej, jest źle sformułowane. [Uwaga: ta obejmuje wywołanie funkcji pośrednio lub jawnie i utworzenie wskaźnika lub wskaźnik do członka do funkcji”

Wywoływanie nadrzędną funkcję wirtualnego poprzez wskaźnik do klasy bazowej jest próbą niejawnie wywołać funkcję. Dlatego, zgodnie z 8.4.3/2, projekt, który na to pozwala, jest nielegalny. Zauważ też, że żaden kompilator zgodny z C++ 11 nie pozwoli ci usunąć nadrzędnej funkcji wirtualnej.

Więcej wyraźnie, tak samo jest upoważniona przez § 10.3/16.

„funkcję z usuniętego definicją (8.4) nie unieważnia funkcję, która nie ma usuniętą definicję Podobnie funkcji, która nie ma usuniętą definicji nie powinny przesłonić funkcję z usuniętego definicji "

+1

Dziękuję. Jakoś tęskniłem za tym klejnotem. Po prostu znalazłem inny cytat, który zabrania mu bezpośrednio: "Funkcja z usuniętą definicją (8.4) nie może zastępować funkcji, która nie ma usuniętej definicji, Podobnie, funkcja, która nie ma usuniętej definicji, nie może nadpisywać funkcji za pomocą usunięta definicja . " – statueuphemism

+0

@ statueuphemism: prawy, dobry punkt. pozwól mi to zintegrować w mojej odpowiedzi –

2

rozważyć kilka funkcji:

void f(LibraryVersion1* p) 
{ 
    p->doSomething1(); 
} 

To kompilacji przed LibraryVersion2 jest nawet napisane.

Więc teraz zaimplementujesz LibraryVersion2 z usuniętym wirtualnym.

f zostało już skompilowane.Nie wie, aż do czasu wykonania, z którego została wywołana podklasa LibraryVersion1.

Dlatego usunięty wirtualny nie jest legalny, nie ma sensu.

Najlepsze co możesz zrobić to:

class LibraryVersion2 : public LibraryVersion1 
{ 
public: 
    virtual void doSomething1() override 
    { 
     throw DeletedFunctionException(); 
    } 
} 
+0

Chciałbym przegłosować to również, gdybym miał taką reputację. Jeśli chodzi o dodatek "najlepsze, co możesz zrobić", to wyrzucenie wyjątku w czasie wykonywania w celu zasygnalizowania przestarzałości metody wydaje mi się kłopotliwe, jeśli chodzi o wycofywanie funkcji (z pewnością nie chciałbym używać biblioteki, która to zrobiła). Myślę, że ta sytuacja wymagałaby przeprojektowania w tym momencie.Ale masz rację, to prawdopodobnie najlepsze, co język może wymusić dla usuniętej definicji nadpisanej metody. – statueuphemism

+0

Nie jest jasne, jaki problem próbujesz rozwiązać. Wygląda na to, że chcesz mieć sposób zarządzania wersjami biblioteki. Została napisana pierwsza biblioteka 1, a następnie aplikacja 1 została napisana przy użyciu biblioteki 1, a następnie zapisana jest biblioteka 2 i użytkownik chce, aby aplikacja 1 nadal działała z biblioteką 2. –

+0

Mój przykład był trochę sztuczny i nie próbuję rozwiązać problemu obecnie. Postawiłem ten pomysł jako potencjalną metodę na utrzymanie dwóch części oprogramowania, w których jedno oprogramowanie dziedziczy bezpośrednio od drugiego bez żadnego dodatkowego scalania (coś w rodzaju Pythona 2.7, który ma pewne podobieństwo z Pythonem 3.0+, ale wciąż jest utrzymywany z powodu znacznych różnic). Jeśli miałbyś utrzymywać klasę Python3_0 dziedziczącą z Python2_7, poprawki błędów w kodzie udostępnionym byłyby automatycznie propagowane. Są jednak inne lepsze projekty, które to umożliwiają. – statueuphemism

4

10.3p16.

funkcja usuniętego z definicją (8.4) nie powinny przesłonić funkcję, która nie posiada dele definicja ted. Podobnie funkcja, która nie ma usuniętej definicji, nie może zastąpić funkcji z usuniętą definicją.

Inne odpowiedzi wyjaśniają, dlaczego całkiem dobrze, ale tam masz oficjalnego, którego nie będziesz.

+0

Aha, i już zacytowałeś to w komentarzu do innej odpowiedzi. W porządku. – aschepler