Если спросить 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 всегда, кроме случаев, когда неявное преобразование семантически оправдано.
Комментарии (9)
Videoman
16.01.2019 16:01В заключение хочется порекомендовать использовать универсальную инициализацию в любом новом коде на C++, а также явно объявлять конструкторы explicit всегда, кроме случаев, когда неявное преобразование семантически оправдано.
Вот на мой взгляд, обе рекомендации очень категоричны. Первая из-за того, что конструкторы принимающие списки инициализации имеют очень высокий приоритет и при перегрузке можно отхватить массу неожиданностей. Второй совет я бы перефразировал с точностью до наоборот: объявлять конструкторы без explicit всегда, кроме случаев, когда преобразование явно семантически не оправдано. Иначе можно совсем лишиться синтаксического сахара и превратиться в яву. Да и стандартная библиотека делает именно так.ellipsis
18.01.2019 18:12на cppCoreGuidelines есть такие рекомендации на тему инициализации:
github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es23-prefer-the--initializer-syntax
github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es64-use-the-tenotation-for-constructionVideoman
18.01.2019 18:30Действительно, многие авторитетные люди советуют использовать фигурные скобки для инициализации (uniform syntax), но нужно понимать, что подход относительно новый и нужно больше практики, что бы понять все его плюсы и минусы. Лично для меня такой синтаксис уродлив, а учитывая приоритет выбора перегрузки для списка инициализации еще и опасен. А вообще нужно понимать, что это типичный holly war.
tbl
17.01.2019 08:39Без объяснения, почему хороша юниформная инициализация, выглядит так, что к существующему зоопарку инициализаций добавили еще одну тварь.
MSerhiy
17.01.2019 11:08Похоже uniform initialization добавил много новых зверей, а приоритет initializer list вообще убивает, неужеле нельзя было сделать его явным.
amlet
17.01.2019 18:21+1Забыли ещё упомянуть, что для explicit оператора приведения к bool не нужно делать явного преобразования при применении в if и while выражениях:
class Simple { public: operator bool() const { return true; } }; class SimpleExplicit { public: explicit operator bool() const { return true; } }; int main(int, char**) { Simple s; bool b = s; // OK SimpleExplicit se; // bool a = se; // COMPILE ERROR bool c = static_cast<bool>(se); // OK // BUT... if(se) // OK - no need explicit cast to bool { }; return 0; }
nikitaevg
Напомнило вот это.
А по теме не соглашусь с рекомендацией использовать такой способ инициализации в любом новом коде. Иногда поведение таких конструкторов очень далеко от интуитивно понятного (я про конструкторы от initializer_list). Скотт Майерс в интервью Яндексу называл эту фичу одной из самых спорных.
igorsemenov Автор
Любопытно. А можно ссылку на это интервью?
nikitaevg
habr.com/ru/company/yandex/blog/241601