Явный конструктор с несколькими аргументами

88

Имеет ли explicit(полезный) эффект создание конструктора с несколькими аргументами ?

Пример:

class A {
    public:
        explicit A( int b, int c ); // does explicit have any (useful) effect?
};
Питер Г.
источник

Ответы:

120

Вплоть до C ++ 11, да, нет причин использовать конструктор explicitс несколькими аргументами.

Это изменилось в C ++ 11 из-за списков инициализаторов. По сути, инициализация копирования (но не прямая инициализация) со списком инициализаторов требует, чтобы конструктор не был отмечен explicit.

Пример:

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
Снефтель
источник
5
Я думаю, что этот ответ был бы лучше с объяснением «Зачем мне это?» Или «Когда это полезно».
MateuszL
@MateuszL Ответ Эдгара, вероятно, лучший аргумент в пользу того, почему он может быть полезен (и, возможно, заслуживает отметки). Причина, по которой он существует , заключается просто в том, что это логическое расширение существующей семантики для explicit. Я бы лично не стал беспокоиться о создании конструкторов с несколькими аргументами explicit.
Sneftel
31

Вы бы наткнулись на него для инициализации скобок (например, в массивах)

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;
}
Рассказчик - Незеленка Моника
источник
24

Отличные ответы @StoryTeller и @Sneftel - главная причина. Однако, IMHO, это имеет смысл (по крайней мере, я это делаю), как часть будущей проверки последующих изменений кода. Рассмотрим свой пример:

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

Этот код не получает прямой выгоды от explicit.

Некоторое время спустя вы решаете добавить значение по умолчанию для c, поэтому оно становится следующим:

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

При этом вы сосредотачиваетесь на cпараметре - оглядываясь назад, он должен иметь значение по умолчанию. Вы не обязательно сосредотачиваетесь на том A, следует ли неявно конструировать саму себя. К сожалению, это изменение explicitснова актуально.

Итак, чтобы передать, что ctor есть explicit, это может быть полезно при первом написании метода.

Ами Тавори
источник
Но как насчет случая, когда сопровождающий добавляет это значение по умолчанию и приходит к выводу, что результат должен быть доступен как конструктор преобразования? Теперь они должны удалить то, explicitчто было там вечно, и техподдержка будет завалена звонками об этом изменении и часами объяснять, что это explicitбыл просто шум, и что его удаление безвредно. Лично я не очень хорошо умею предсказывать будущее; Сейчас довольно сложно решить, как должен выглядеть интерфейс .
Пит Беккер,
@PeteBecker Это хороший аргумент. Я лично считаю, что эти два случая асимметричны, и что гораздо чаще при настройке параметров по умолчанию (или их удалении) непреднамеренно сделать класс неявно конструируемым, а затем в то же время фактически осознать, что ретроспективно это должно быть так. При этом это «мягкие» соображения, которые могут варьироваться в зависимости от людей / проектов / и т. Д., Или даже просто дело вкуса.
Ами Тавори,
8

Вот мои пять центов за это обсуждение:

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

Как видите, не explicitпозволяет использовать список инициализаторов вместе с barфункцией, потому что конструктор struct Barобъявлен как explicit.

Эдгар Рокьян
источник