Jawny konstruktor pobierający wiele argumentów

88

Czy tworzenie konstruktora z wieloma argumentami explicitma jakiś (użyteczny) efekt?

Przykład:

class A {
    public:
        explicit A( int b, int c ); // does explicit have any (useful) effect?
};
Peter G.
źródło

Odpowiedzi:

120

Aż do C ++ 11, tak, nie ma powodu, aby używać explicitna konstruktorze z wieloma argumentami.

To się zmienia w C ++ 11 z powodu list inicjalizujących. Zasadniczo inicjalizacja kopiowania (ale nie inicjalizacja bezpośrednia) z listą inicjatorów wymaga, aby konstruktor nie był oznaczony explicit.

Przykład:

struct Foo { Foo(int, int); };
struct Bar { explicit Bar(int, int); };

Foo f1(1, 1); // ok
Foo f2 {1, 1}; // ok
Foo f3 = {1, 1}; // ok

Bar b1(1, 1); // ok
Bar b2 {1, 1}; // ok
Bar b3 = {1, 1}; // NOT OKAY
Sneftel
źródło
5
Myślę, że ta odpowiedź byłaby lepsza z wyjaśnieniem „Dlaczego miałbym tego chcieć” lub „Kiedy jest to przydatne”.
MateuszL
@MateuszL Odpowiedź Edgara jest prawdopodobnie najlepszym argumentem za tym, dlaczego może być przydatna (i prawdopodobnie zasługuje na zaznaczenie). Powodem jest to nie jest jednak po prostu dlatego, że to logiczne rozszerzenie istniejących semantyki dla explicit. Osobiście nie zawracałbym sobie głowy tworzeniem konstruktorów wieloskładnikowych explicit.
Sneftel
31

Natknąłbyś się na to podczas inicjalizacji nawiasów klamrowych (na przykład w tablicach)

struct A {
        explicit A( int b, int c ) {}
};

struct B {
         B( int b, int c ) {}
};

int main() {
    B b[] = {{1,2}, {3,5}}; // OK

    A a1[] = {A{1,2}, A{3,4}}; // OK

    A a2[] = {{1,2}, {3,4}}; // Error

    return 0;
}
StoryTeller - Unslander Monica
źródło
24

Głównym powodem są doskonałe odpowiedzi udzielone przez @StoryTeller i @Sneftel. Jednak IMHO, ma to sens (przynajmniej ja to robię), jako część przyszłego sprawdzania późniejszych zmian w kodzie. Rozważ swój przykład:

class A {
    public:
        explicit A( int b, int c ); 
};

Ten kod nie korzysta bezpośrednio z explicit .

Jakiś czas później decydujesz się dodać domyślną wartość dla c, więc staje się ona następująca:

class A {
    public:
        A( int b, int c=0 ); 
};

Robiąc to, koncentrujesz się na cparametrze - z perspektywy czasu powinien on mieć wartość domyślną. Niekoniecznie koncentrujesz się na tym, czy Asam powinien być skonstruowany w sposób niejawny. Niestety, ta zmiana explicitznów ma znaczenie.

Tak więc, aby pokazać explicit, że ctor jest , warto byłoby to zrobić podczas pierwszego pisania metody.

Ami Tavory
źródło
Ale co w przypadku, gdy opiekun dodaje to domyślne i stwierdza, że ​​wynik powinien być dostępny jako konstruktor konwertujący? Teraz muszą usunąć to explicit, co było tam od zawsze, a pomoc techniczna będzie zalewana wezwaniami na temat tej zmiany i spędzać godziny na wyjaśnianiu, że explicitto tylko hałas, a usuwanie go jest nieszkodliwe. Osobiście nie jestem dobry w przepowiadaniu przyszłości; Trudno zdecydować, co na tyle interfejs powinien wyglądać teraz .
Pete Becker
@PeteBecker To dobra uwaga. Osobiście uważam, że te dwa przypadki są asymetryczne i że przy ustawianiu parametrów jako domyślnych (lub ich usuwaniu) o wiele częściej zdarza się nieumyślnie uczynić klasę niejawnie konstruowalną, a jednocześnie zdać sobie sprawę, że z perspektywy czasu powinno tak być. Biorąc to pod uwagę, są to względy „miękkie” i mogą się różnić w zależności od ludzi / projektów / itp., A nawet mogą być po prostu kwestią gustu.
Ami Tavory
8

Oto moje pięć centów do tej dyskusji:

struct Foo {
    Foo(int, double) {}
};

struct Bar {
    explicit Bar(int, double) {}
};

void foo(const Foo&) {}
void bar(const Bar&) {}

int main(int argc, char * argv[]) {
    foo({ 42, 42.42 }); // valid
    bar({ 42, 42.42 }); // invalid
    return 0;
}

Jak łatwo zauważyć, explicitzapobiega używaniu listy inicjalizującej razem z barfunkcją, ponieważ konstruktor struct Barjest zadeklarowany jako explicit.

Edgar Rokjān
źródło