Construtor explícito com vários argumentos

88

Fazer um construtor com vários argumentos explicittem algum efeito (útil)?

Exemplo:

class A {
    public:
        explicit A( int b, int c ); // does explicit have any (useful) effect?
};
Peter G.
fonte

Respostas:

120

Até C ++ 11, sim, não há razão para usar explicitem um construtor multi-arg.

Isso muda no C ++ 11, por causa das listas de inicializadores. Basicamente, a inicialização de cópia (mas não a inicialização direta) com uma lista de inicializadores requer que o construtor não seja marcado explicit.

Exemplo:

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
fonte
5
Acho que essa resposta seria melhor com a explicação "Por que eu iria querer isso" ou "Quando isso é útil".
MateuszL
@MateuszL A resposta de Edgar fornece provavelmente o melhor argumento para explicar por que pode ser útil (e sem dúvida merece o visto). A razão de estar , no entanto, é simplesmente porque é a extensão lógica da semântica existente para explicit. Eu pessoalmente não me incomodaria em fazer construtores multi-arg explicit.
Sneftel
31

Você tropeçaria nele para a inicialização da chave (por exemplo, em matrizes)

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
fonte
24

As excelentes respostas de @StoryTeller e @Sneftel são o principal motivo. No entanto, IMHO, isso faz sentido (pelo menos eu faço isso), como parte da verificação futura de alterações posteriores no código. Considere seu exemplo:

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

Este código não se beneficia diretamente de explicit.

Algum tempo depois, você decide adicionar um valor padrão para c, então ele se torna este:

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

Ao fazer isso, você está se concentrando no cparâmetro - em retrospecto, ele deve ter um valor padrão. Você não está necessariamente focando se Aele deve ser construído implicitamente. Infelizmente, essa mudança torna explicitrelevante novamente.

Portanto, para transmitir que um ctor é explicit, pode valer a pena fazê-lo ao escrever o método pela primeira vez.

Ami Tavory
fonte
Mas e o caso em que o mantenedor adiciona esse padrão e conclui que o resultado deve estar disponível como um construtor de conversão? Agora, eles precisam remover o explicitque está lá há muito tempo, e o suporte técnico será inundado com ligações sobre essa mudança e passará horas explicando que explicitera apenas ruído e que removê-lo é inofensivo. Pessoalmente, não sou muito bom em prever o futuro; já é difícil decidir como uma interface deve ser agora .
Pete Becker de
@PeteBecker Esse é um bom ponto. Eu pessoalmente acho que os dois casos são assimétricos e que é muito mais comum ao tornar os parâmetros padrão (ou removê-los) inadvertidamente tornar a classe implicitamente construtível, então perceber ao mesmo tempo que em retrospecto deveria ser assim. Dito isso, essas são considerações "suaves" e podem variar entre pessoas / projetos / etc., Ou mesmo ser apenas uma questão de gosto.
Ami Tavory
8

Aqui estão meus cinco centavos para esta discussão:

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;
}

Como você pode ver facilmente, explicitevita o uso da lista de inicializadores juntamente com a barfunção porque o construtor de struct Baré declarado como explicit.

Edgar Rokjān
fonte