Как стать автором
Обновить

explicit в деталях

Время на прочтение 3 мин
Количество просмотров 144K

Если спросить C++-программиста о значении ключевого слова explicit, большинство ответит, что это ключевое слово ставится перед объявлением конструктора с одним параметром (или с большим числом параметров, но когда все параметры, начиная со второго, имеют значения по умолчанию) и предотвращает неявное преобразование типов при инициализации.


class Simple {
public:
    Simple(int a) : a_(a) {}
private:
    int a_;
};

class SimpleExplicit {
public:
    explicit SimpleExplicit(int a) : a_(a) {}
private:
    int a_;
};

template <typename S>
void someFunc(const S& s) {
}

int main(int, char**) {
    Simple s3 = 11;
    // SimpleExplicit se3 = 11; - COMPILE ERROR
    SimpleExplicit se3 = SimpleExplicit(11);

    someFunc<Simple>(11);
    // someFunc<SimpleExplicit>(11); - COMPILE ERROR
    someFunc<SimpleExplicit>(SimpleExplicit(11));

    return 0;
}

В старом добром C++03 сценарии применения ключевого слова на этом заканчивались, однако, начиная с C++11, область применения explicit расширилась: теперь оно имеет смысл не только в конструкторах с одним параметром, и даже не только в конструкторах.


В 2011 году в Стандарт добавили универсальную инициализацию (uniform initialization), которая должна навести порядок в зоопарке способов инициализации объектов, доставшемся C++ в наследство от языка C. Я не буду здесь подробно рассказывать про универсальную инициализацию, на эту тему есть множество подробных статей, их несложно найти по ключевым словам. В двух словах: объекты предлагается инициализировать при помощи фигурных скобок, по сути это расширение т.н. агрегатной инициализации (aggregate initialization), унаследованной ещё со времён C.


С появлением универсальной инициализации explicit обрёл смысл для конструкторов с 0,2,3 и более параметров:


class Simple {
public:
    Simple() : a_(0), b_(0) {}
    Simple(int a) : a_(a), b_(0) {}
    Simple(int a, int b) : a_(a), b_(b) {}
private:
    int a_, b_;
};

class SimpleExplicit {
public:
    explicit SimpleExplicit() : a_(0), b_(0) {}
    explicit SimpleExplicit(int a) : a_(a), b_(0) {}
    explicit SimpleExplicit(int a, int b) : a_(a), b_(b) {}
private:
    int a_, b_;
};

template <typename S>
void someFunc(const S& s) {
}

int main(int, char**) {
    Simple s4 = {};
    someFunc<Simple>({});
    // SimpleExplicit se4 = {}; - COMPILE ERROR
    SimpleExplicit se4 = SimpleExplicit{};
    // someFunc<SimpleExplicit>({}); - COMPILE ERROR
    someFunc<SimpleExplicit>(SimpleExplicit{});

    Simple s5 = {11};
    someFunc<Simple>({11});
    // SimpleExplicit se5 = {11}; - COMPILE ERROR
    SimpleExplicit se5 = SimpleExplicit{11};
    // someFunc<SimpleExplicit>({11}); - COMPILE ERROR
    someFunc<SimpleExplicit>(SimpleExplicit{11});

    Simple s6 = {11, 22};
    someFunc<Simple>({11, 22});
    // SimpleExplicit se6 = {11, 22}; - COMPILE ERROR
    SimpleExplicit se6 = SimpleExplicit{11, 22};
    // someFunc<SimpleExplicit>({11, 22}); - COMPILE ERROR
    someFunc<SimpleExplicit>(SimpleExplicit{11, 22});

    return 0;
}

Помимо этого, начиная с C++11 ключевое слово explicit может также применяться к операторам преобразования типа, также запрещая их неявный вызов:


class Simple {
public:
    Simple() {}
    operator bool() const { return true; }
};

class SimpleExplicit {
public:
    explicit SimpleExplicit() {}
    explicit operator bool() const { return true; }
};

int main(int, char**) {
    Simple s7{};
    bool b7 = s7;

    SimpleExplicit se7{};
    // bool be7 = se7; - COMPILE ERROR
    bool be7 = static_cast<bool>(se7);

    return 0;
}

В заключение хочется порекомендовать использовать универсальную инициализацию в любом новом коде на C++, а также явно объявлять конструкторы explicit всегда, кроме случаев, когда неявное преобразование семантически оправдано.

Теги:
Хабы:
Всего голосов 30: ↑30 и ↓0 +30
Комментарии 9
Комментарии Комментарии 9

Публикации

Истории

Работа

Программист C++
128 вакансий
QT разработчик
15 вакансий