2015-07-28 22 views
12

C++ Primer (5a edizione) a pagina 629 afferma:Perché il costruttore ereditato non dovrebbe ereditare gli argomenti predefiniti?

  • Se un costruttore della classe base ha argomenti di default, tali argomenti non vengono ereditati. Invece, la classe derivata ottiene più costruttori ereditati in cui ogni parametro con un argomento predefinito viene omesso successivamente.

Qual è il ragionamento alla base di questa regola?

+2

Mi azzardo a supporre che, poiché i parametri predefiniti possono essere specificati solo una volta, una classe derivata non può ereditare tecnicamente un costruttore con parametri predefiniti, poiché il compilatore non saprebbe quale dei set di valori predefiniti utilizzare. –

+1

Se ho capito bene, intendi che non ci dovrebbe essere una contraddizione tra le due definizioni. Ma qui nessuna contraddizione è possibile ... – AlwaysLearning

+0

Mi azzarderei ad indovinare che facendo così eviti di fare copie extra quando passi quei parametri inizializzati con argomento predefinito a un costruttore di una classe base –

risposta

9

Data l'attuale formulazione; Penso che sia specificato in questi termini (§12.9/1 di C++ WD n4527) per pochi motivi (ma principalmente per evitare potenziali ambiguità);

  1. Evitare l'ambiguità. Cioè se si inizia a fornire ai propri costruttori parametri di corrispondenza, ciò consentirà a questi costruttori di non entrare in conflitto (essere ambigui) con i costruttori ereditati
  2. Mantenere l'effetto della stessa. Cioè come apparirebbe il codice del client

Il inheriting constructors è una tecnica simile alla generazione di codice ("Voglio ciò che ha la mia base"). Non c'è modo di specificare quali costruttori si stanno ottenendo, in pratica si ottengono tutti da qui al compilatore facendo molta attenzione a non generare costruttori ambigui.

A titolo di esempio;

#include <iostream> 
using namespace std; 
struct Base { 
    Base (int a = 0, int b = 1) { cout << "Base" << a << b << endl; } 
}; 
struct Derived : Base { 
    // This would be ambiguous if the inherited constructor was Derived(int=0,int=1) 
    Derived(int c) { cout << "Derived" << c << endl; } 
    using Base::Base; 
}; 
int main() 
{ 
    Derived d1(3); 
    Derived d2(4,5); 
} 

Uscite;

Base01
Derived3
Base45

Sample code.


C'è una proposta fuori n4429 (come notato da Jonathan Wakely) per un cambiamento nella formulazione intorno ai costruttori Ereditare e l'utilizzo di dichiarazione per le classi.

Dato l'intento della proposta;

... questa proposta fa ereditare un atto di costruttore proprio come ereditare qualsiasi altro membro della classe base, per quanto possibile.

C'è il seguente cambiamento (nuova formulazione);

Variazione 7.3.3 namespace.udecl paragrafo 15:

Quando un dichiarazione using porta dichiarazioni da una classe base in una classe derivata ... Queste dichiarazioni nascoste o sostituire sono esclusi dal serie di dichiarazioni introdotte dalla dichiarazione d'uso.

E ne segue immediatamente un esempio che tratta direttamente i costruttori (sebbene senza argomenti di default);

struct B1 { 
    B1(int); 
}; 

struct B2 { 
    B2(int); 
}; 

struct D1 : B1, B2 { 
    using B1::B1; 
    using B2::B2; 
}; 
D1 d1(0); // ill-formed: ambiguous 

struct D2 : B1, B2 { 
    using B1::B1; 
    using B2::B2; 
    D2(int); // OK: D2::D2(int) hides B1::B1(int) and B2::B2(int) 
}; 
D2 d2(0); // calls D2::D2(int) 

In breve, mentre probabilmente non la formulazione finale, sembra che l'intento è quello di permettere ai costruttori per essere utilizzati con i loro argomenti di default ed esplicitamente esclude dichiarazioni nascosti e ignorati quindi credo che prendersi cura di qualsiasi ambiguità. Sembra che la formulazione semplifichi lo standard, ma produca lo stesso risultato. viene utilizzato nel codice client.

+1

Ma per lo stesso token si potrebbe definire un altro costruttore in Base prendendo un 'int', il che si tradurrebbe in una simile opportunità per l'ambiguità. Perché lo standard ha improvvisamente deciso di proteggermi nel caso dell'eredità? – AlwaysLearning

+0

@ MeirGoldenberg. Sì, non cambia nulla, ma ciò comporterà un errore di compilazione con codice che è tipizzato esplicitamente (cioè non generato dal compilatore), i costruttori ereditari sono una tecnica simile alla generazione di codice ("Voglio ciò che ha la mia base"). Non c'è modo di specificare quali costruttori si stanno ottenendo, in pratica si ottengono tutti da qui al compilatore facendo molta attenzione a non generare costruttori ambigui. – Niall

+1

Chiaramente la risposta corretta! – Walter

6

Gli argomenti predefiniti non fanno parte della firma di una funzione e possono essere aggiunti in seguito e in un ambito limitato, che non sarebbe in grado di modificare i costruttori già definiti di una classe derivata, ad es.

// in A.h 
struct A { 
    A(int, int); 
}; 

// in B.h 
#include "A.h" 
struct B : A { 
    using A::A; 
}; 

// in A.cc 
#include "A.h" 
A::A(int, int = 0) { } 

Nel file A.cc si può creare un A con un solo parametro, perché l'argomento di default è visibile, ma quando B è stata dichiarata l'argomento di default non era visibile, quindi non può essere considerato quando ereditare i costruttori. Credo che questa sia una delle ragioni per cui le argomentazioni di default ottengono un trattamento speciale.

Anche se a quanto pare come costruttori di ereditare il lavoro potrebbe essere destinato a cambiare, e argomenti di default non sarebbe ottenere questo trattamento speciale, vedere http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4429.html

+0

Ooops. Ho pensato che gli argomenti predefiniti devono essere forniti nella dichiarazione della funzione (in 'Ah' nel caso di' A :: A (int, int) ') e non possono essere forniti in seguito (nella definizione della funzione in' A.cc') o mi sbaglio? – Walter

+1

@Walter, ti sbagli. Per i modelli di costruttore devono essere forniti nella dichiarazione, ma non per i costruttori non di modello. –

2

Qual è il ragionamento che sta dietro questa regola?

Potrebbe impedire cambiamenti dell'argomento predefinito dalla classe di base che influenzano il comportamento di tutta la classe derivata (all'interno della portata classe derivata), che sarà una sorpresa per i creatori classe derivata.

0

Heads up, proprio come ha detto Jonathan Wakely, C++ 17 ha modificato questo comportamento. Ora gli argomenti predefiniti nell'elenco dei parametri vengono ereditati.

Vale a dire, se avessimo il seguente costruttore in una classe denominata Base,

struct Base { 
    Base(int a, int b, int c = 1, int d = 2, int e = 3) {} 
}; 

poi per quel costruttore sopra, questi sono quelli che sono 'iniettati' nella classe derivata in C + corrispondente + 11/C++ 14:

struct Derived : Base { 
    using Base::Base; 
    /* 
    C++11/C++14: 
    Derived::Derived(int a, int b) : Base(a, b) {} 
    Derived::Derived(int a, int b, int c) : Base(a, b, c) {}  
    Derived::Derived(int a, int b, int c, int d) : Base(a, b, c, d) {} 
    Derived::Derived(int a, int b, int c, int d, int e) : Base(a, b, c, d, e) {} 
    */ 
}; 

mentre quello in C++ 17 è ora molto più semplice:

struct Derived : Base { 
    using Base::Base; 
    /* 
    C++17: 
    Derived::Derived(int a, int b, int c = 1, int d = 2, int e = 3) : Base(a, b, c, d, e) {} 
    */ 
}; 

Perché credo che questo sia:

Sulla base del cppreference.com page sui costruttori che ereditano e la carta che ha introdotto le modifiche (P0136R1), l'intero [class.inhctor]\1 sottosezione, che specificate come i costruttori ereditate vengono divisi e 'iniettato' nel la classe derivata è stata rimossa. (In effetti, l'intera sezione [class.inhctor] è stata rimossa).E 'stato poi sostituito con una semplice regola in [namespace.udecl]\16 in C++ 17, che dice (sottolineatura mia):

Ai fini della risoluzione di sovraccarico, le funzioni che vengono introdotte da una dichiarazione using in un derivato Le classi vengono considerate come se fossero membri della classe derivata. In particolare, implicitamente questo parametro deve essere trattato come se fosse un puntatore alla classe derivata piuttosto che alla classe base. Ciò non ha alcun effetto su il tipo di funzione e, in tutti gli altri aspetti, la funzione rimane un membro della classe base. Analogamente, i costruttori introdotti da una dichiarazione di utilizzo vengono trattati come se fossero costruttori della classe derivata durante la ricerca dei costruttori della classe derivata (6.4.3.1) o della formazione di un gruppo di sovraccarico candidati (16.3 .1.3, 16.3.1.4, 16.3.1.7). Se viene selezionato un costruttore per eseguire l'inizializzazione di un oggetto di tipo classe, tutti i sottooggetti diversi dalla classe base da cui è stato originato il costruttore sono implicitamente inizializzati (15.6.3).

Quindi l'elenco dei parametri ora "trasporta" completamente. In effetti questa è la mia esperienza con CLion compatibile con P0136R1 con GCC 7.2, mentre il mio Visual Studio 2017 non compatibile con P0136R1 (15.6) mostrava i precedenti costruttori con gli argomenti predefiniti abbandonati.

Problemi correlati