explicit
предназначен для предотвращения переходов неявных. Каждый раз, когда вы используете что-то вроде String(foo);
, это явное преобразование, поэтому использование explicit
не изменит, удастся ли оно или не удастся.
Итак, давайте рассмотрим сценарий, который подразумевает неявное преобразование. Давайте начнем с вашего String
класса:
class String {
public:
explicit String(int n); // allocate n bytes to the String object
String(const char *p); // initializes object with char *p
};
Тогда давайте определим функцию, которая принимает параметр типа String
(также может быть String const &
, но String
будет делать на данный момент):
int f(String);
Ваши Конструкторы позволяют неявное преобразование от char const *
, но только явное преобразование от int
. Это означает, что, если я позвоню:
f("this is a string");
... компилятор будет генерировать код для создания объекта String, из строкового литерала, а затем вызвать f
с этим String
объекта.
Если, однако, вы пытаетесь вызвать:
f(2);
Он потерпит неудачу, потому что String
конструктор, который принимает int
параметр был отмечен explicit
.Это означает, что, если я хочу, чтобы преобразовать int
в String
, я должен сделать это явно:
f(String(2));
Если String(char const *);
конструктор был также отмечен explicit
, то вы бы не быть в состоянии назвать f("this is a string")
либо - вам 'd необходимо использовать f(String("this is a string"));
Обратите внимание, что explicit
только контролирует неявное преобразование из определенного типа foo
в тип, который вы определили. Он не влияет на неявное преобразование из другого типа в типа вашего explicit
. Таким образом, ваш явный конструктор, который принимает тип int
будет еще параметр с плавающей точкой:
f(String(1.2))
... потому что предполагает неявное преобразование из double
в int
с последующим явным приведением из int
в String
. Если вы хотите запретить преобразование из double
в String
, вы бы сделать это (например) обеспечение перегруженного конструктора, который принимает double
, но потом бросаешь:
String(double) { throw("Conversion from double not allowed"); }
Теперь неявное преобразование из double
в int
не произойдет - double
будет передаваться непосредственно на ваш ctor без преобразования.
Как к тому, что с помощью explicit
выполняет: основной смысл использования explicit
является предотвратить кода при компиляции, которые могли бы составить. В сочетании с перегрузкой неявные преобразования могут иногда приводить к некоторым довольно странным выборам.
Проще показать проблему с операторами преобразования, а не с конструкторами (потому что вы можете сделать это только с одним классом). Например, давайте рассмотрим маленький класс строки очень похож на много, которые были написаны, прежде чем люди поняли, как много о том, как проблематично неявное преобразование может быть:
class Foo {
std::string data;
public:
Foo(char const *s) : data(s) { }
Foo operator+(Foo const &other) { return (data + other.data).c_str(); }
operator char const *() { return data.c_str(); }
};
(я изменял с помощью std::string
для хранения данных, но то же самое было бы верно, если бы я сделал это, и сохранил char *
, и использовал new
для выделения памяти).
Теперь это делает вещи, как эта работа штрафа:
Foo a("a");
Foo b("b");
std::cout << a + b;
... и (конечно же) результат в том, что он печатает ab
. Но что произойдет, если пользователь допустит небольшую ошибку и типа -
, где они намеревались ввести +
?
Вот где вещи становятся уродливыми - код все еще компилируется и «работает» (для некоторого определения слова), но печатает глупости. В ходе быстрого теста на моей машине я получил -24
, но не рассчитываю на дублирование этого конкретного результата.
Проблема здесь связана с допущением неявного преобразования из String
в char *
. Когда мы пытаемся вычесть два объекта String
, компилятор пытается понять, что мы имели в виду.Поскольку он не может вычитать их напрямую, он смотрит, может ли он преобразовать их в какой-то тип, который поддерживает вычитание, и, конечно же, char const *
поддерживает вычитание, поэтому он преобразует оба наших объекта String
в char const *
, а затем вычитает два указателя.
Если мы отмечаем, что преобразование как explicit
вместо:
explicit operator char const *() { return data.c_str(); }
... код, который пытается вычесть два String
объекты просто не скомпилируется.
Эта же основная идея может быть применима к explicit
конструкторам, но код для демонстрации ее становится длиннее, потому что нам обычно требуется как минимум несколько различных классов.
Ожидаете ли вы 'String ('x')' для вызова 'String (const char *)'? '' x'' является символом 'char', а не' char'. Во всяком случае, вы только показываете * явные * конструкции 'String', поэтому' explicit' не имеет значения. Попробуйте вызвать функцию, которая принимает 'String' с' int', и она не будет работать, потому что преобразование 'int' →' String' не может быть сделано * неявно *. – Biffen
'String mystring = 'x';' [does _not_ work] (http://coliru.stacked-crooked.com/a/79af8ce27adf8a61). – cpplearner
@cpplearner Я знаю об этом, поэтому я указал, что я не использую это слово явно. В вашем примере вы используете явно –