Пожалуйста, рассмотрите следующий код:
class Foo {
public:
explicit Foo(double) {}
};
Foo * test();
Foo * test() {
return new Foo(Foo(1.0)); // (1)
}
Мой вопрос касается строки (1). Это очень похоже на ошибку, которая заняла у меня некоторое время, чтобы выследить. Я не заметил, что тип был указан дважды из-за ошибки копирования / вставки. Правильная строка очевидно:
return new Foo(1.0);
Интересно, что это изменение также компилируется без предупреждения:
return new Foo(Foo(Foo(Foo(1.0))));
Почему эти примеры компилируются без предупреждений с помощью clang, даже если -Wall -Weverything
флаги? Почему Foo::Foo(double)
принять экземпляр Foo в качестве действительного double
аргумент? Это какое-то своеобразное поведение оператора нового?
Мой оригинальный код был в более широком контексте и протестирован с двумя компиляторами на основе LLVM-3. Оба скомпилированы без предупреждений или ошибок. С одним, код фактически функционировал, как я ожидал, и на самом деле я не осознавал, что в течение некоторого времени была ошибка. С другой стороны, экземпляр Foo вел себя очень странно — я не могу описать его должным образом — это было так, как если бы более поздняя копия возвращенного указателя «волшебным образом» стала отличаться от оригинала, что привело к несовпадению состояний между двумя взаимодействующими объекты, которые должны были содержать эквивалентные указатели на общий Foo, но по какой-то причине содержали разные значения после присваивания. Пока я не понимаю, что здесь происходит, это кажется действительно странным!
Интересно, что следующие компиляторы с обоими компиляторами:
class Foo { public: explicit Foo(double) {} };
class Bar { public: explicit Bar(double) {} };
Foo * testFoo() { return new Foo(Foo(1.0)); }
Bar * testBar() { return new Bar(Bar(1.0)); }
Но следующая версия не делает:
Foo * testFooBar() { return new Foo(Bar(1.0)); }
Компилятор автоматически генерирует конструктор копирования Foo(const Foo&)
и, возможно, также перемещать конструктор в зависимости от точной версии / настроек Foo(Foo&&)
, Это не имеет ничего общего с оператором new или магией указателя. Ваш код просто вызывает совершенно нормальный конструктор копирования / перемещения, определенный для вас компилятором. Это все. Такое поведение предписано стандартом. Некодируемый класс (по крайней мере, в оригинальной версии Стандарта) практически бесполезен.
Если вам не нужны автоматически сгенерированные конструкторы копирования, обычная техника — определить их как удаленные или частные.
Также обратите внимание, что компилятор в некоторых случаях имеет право фактически исключать целые объекты из вашей программы, хотя обычно это не должно быть. Если вы выполняете переходы с указателем на Foo, установленным в конструкторе Foo, вы должны строго обрабатывать их во всех конструкторах и деструкторах, то есть иметь необходимость писать свои собственные конструкторы копирования / перемещения, деструкторы и операторы присваивания, иначе вы придете обрезка, когда компилятор исключает объект.
Конструктор, который вы наблюдаете, является конструктором копирования. У каждого класса есть конструктор копирования. Если вы не объявляете конструктор копирования в своем определении класса, конструктор копирования неявно объявлено для тебя.
Конкретная сигнатура неявно объявленного конструктора копирования зависит от определения вашего класса, то есть от основ и членов — как правило, имеет вид X::X(X const &)
, но это может быть X::X(X &)
или же X::X(X volatile &)
если необходимо. Спецификации исключений максимально жесткие. Определение неявно объявленного конструктора копирования обычно состоит из копирования баз и членов, но при определенных обстоятельствах его можно определить как удаленное (например, если вы объявляете конструктор перемещения).
(В будущих версиях C ++, если вы объявите оператор присваивания копирования или перемещения, неявно объявленный конструктор копирования будет определен как удаленный. В C ++ 11 и C ++ 14 он используется по умолчанию.)