#cpp
Вот код с примером #includeclass Object { public: Object(); }; class scoped_ptr { Object *pObj; public: scoped_ptr(Object *pObject); }; Object::Object() { } scoped_ptr::scoped_ptr(Object *pObject) { pObj = pObject; } int main() { scoped_ptr p = new Object(); } То есть это не указатель scoped_ptr p = new Object(); и все ок. А вот указатель Object *p = new Object(); и тоже все ок. Почему так происходит? По первому варианту scoped_ptr p = new Object(); я вижу это так: сначала выделяется память с помощью new по размеру объекта; Cоздается объект типа Object c помощью конструктора Object(); Далее объект p выстраивается по объекту типа Object, а если точнее, то в конструкторе копирования просто получает адрес принятого объекта и присваивает его указателю на объект *pObj. Ну вот и объект "p" будет не указателем на объект, а просто объектом. А если бы было так scoped_ptr *p = new Object();, то была бы ошибка, потому что *p был бы указатем на один тип, а ему пытались присвоить адрес с другим типом. По второму варианту Object *p = new Object(); я вижу это так: выделение памяти; создание объекта по конструктору Object(). Конструктор копирования не запускается, так как "*p" - это указатель на объект и ему сразу присваивается адрес. Правильно ли я понял или где-то ошибся? И ещё слышал, что запись вида scoped_ptr p = new Object();, а не scoped_ptr p(new Object()); можно делать потому, что тип аргумента конструктора копий приводится к типу класса, если есть только один аргумент и по этому можно присваивать объектам данные того типа, который был в аргументе до неявного преобразования и по этому работает запись через "=", но в какой момент срабатывает это неявное преобразование? Ведь если бы в начале, то получилось бы что pObj = pObject; pObj - указывает на тип Object, а pObject - указывает на тип scoped_ptr и была бы ошибка. Значит преобразование вообще не происходит? Тогда и это игнорируется scoped_ptr p = scoped_ptr(new Object());? Если это явное приведение типов происходит, то как именно, создается временная копия объекта с этим типом и зачем она нужна, если параметр конструктора копий требует другой тип?
Ответы
Ответ 1
Ну вроде бы вы всё поняли почти правильно. На самом деле, указатель или не указатель, не имеет значения, правила для всех одинаковы. Смотрите. Когда вы пишете X x = y; происходит совсем не то, что если бы вы написали X x; x = y; В первом случае происходит инициализация объекта x, во втором случае -- присваивание. Для инициализации используется конструктор копирования. Это значит, что записи X x = y; и X x(y); равнозначны, и понимаются компилятором одинаково: в обоих случаях будет вызван конструктор копирования из типа Y (который является типом выражения y). Рассмотрим ваш пример. Код scoped_ptr p = new Object(); вызывает выполнение конструктора копирования типа scoped_ptr с аргументом типа Object* (ведь тип выражения new Object() как раз Object*). А код Object *p = new Object(); точно так же вызывает конструктор копирования типа Object* (объект p у нас имеет тип Object*, вас не должно смущать, что звёздочка написана около p, на самом деле это всё равно что Object* p). Для типа указатель (которым является Object*) неявно определён конструктор копирования из указателя приводимого типа -- он просто копирует значение адреса. Код scoped_ptr *p = new Object(); бы не скомпилировался потому, что у типа scoped_ptr* нету конструктора копирования из типа Object*, ведь типы scoped_ptr и Object не родственны (а конструктор копирования для указателей есть только для родственных типов). Записи scoped_ptr p = new Object(); и scoped_ptr p(new Object()); абсолютно равносильны, никакого неявного преобразования тут не происходит. Заметьте, что с присваиванием немного сложнее. Если компилятор видит код p = new Object(); где p имеет тип scoped_ptr, он пытается применить существующий оператор присваивания. Но у нас не определён явно оператор присваивания, и в этом случае C++ создаёт для нас оператор присваивания по умолчанию, который выглядит примерно так: scoped_ptr& scoped_ptr::operator=(const scoped_ptr& other) { pObj = other.pObj; return *this; } Итак, C++ пытается применить этот оператор присваивания, но тип нашего аргумента -- Object*, а не scoped_ptr. В этом случае компилятор проверяет, а нельзя ли сделать неявное преобразование типа Object* в scoped_ptr? Для такого преобразования подходит конструктор scoped_ptr с типом аргумента Object* (если он не объявлен со специальным ключевым словом explicit, которое предотвращает неявное преобразование). Итак, у нас сначала вызовется конструктор scoped_ptr(Object*), и результат уйдёт оператору присваивания по умолчанию. Вот вам маленький пример (добавлен оператор присваивания, выводящий текст): http://ideone.com/968YYk. Необходимое уточнение (уголок зануды). На самом деле, конструкции scoped_ptr p = new Object(); и scoped_ptr p(new Object()); всё же немного отличаются. Код scoped_ptr p(new Object()); является вызовом конструктора scoped_ptr с аргументом типа Object*. Среди всех имеющихся конструкторов (их может быть несколько) выбираются подходящие (те, у которых первый аргумент может быть неявно сконвертирован в тип Object*, а остальные аргументы имеют значения по умолчанию), из них выбирается наиболее подходящий (точно так же, как и при нормальном вызове функции). Этот конструктор и вызывается; аргументы при необходимости подвергаются неявной конверсии. В нашем случае у нас два конструктора: с аргументом типа Object* и с аргументом типа const scoped_ptr&, второй конструктор автоматически добавлен компилятором. Из этих конструкторов оба подходят (в первом аргумент точно соответствует, во втором аргумент может быть неявно сконвертирован при помощи, конечно, первого конструктора). Но первый подходит лучше, поэтому он и выбирается. Такая инициализация называется прямой инициализацией. Код scoped_ptr p = new Object(); рассматривается компилятором немного по-другому. Во-первых, при рассмотрении вариантов не учитываются конструкторы с ключевым словом explicit. Это значит, что если бы конструктор scoped_ptr(Object *pObject) был помечен этим ключевым словом, наш код не скомпилировался бы. Далее, компилятор выбирает не любой конструктор, а неявно (или явно) определённый конструктор scoped_ptr& operator=(const scoped_ptr& ptr). (Если этот конструктор удалить или сделать приватным, код не откомпилируется.) Итак, наш аргумент конструктора Object* должен неявно сконвертироваться в scoped_ptr. (Как уже сказано, для этого не подходят конструкторы, помеченные как explicit.) Таким образом, если при прямой инициализации рассматриваются конверсии через любые типы промежуточных аргументов, то здесь подходит лишь конверсия через scoped_ptr. Вот пример, когда эти пути могут быть различны: http://ideone.com/WhBk4R. Финальный вызов копирующего конструктора может быть убран при оптимизации (обычно так и происходит). Такая инициализация называется копирующей инициализацией. (Спасибо @Котик'у, указавшему на тонкость с explicit.) Некоторые идеи подсмотрены на SO, советую почитать, кто хочет больше подробностей и ссылок на стандарт.
Комментариев нет:
Отправить комментарий