При передаче объекта по значению в функцию в C++ происходит создание копии объекта с помощью конструктора копирования. Если в классе определён пользовательский конструктор копирования, то он вызывается для инициализации временного объекта-аргумента функции. Если не определён — используется компилятор по умолчанию, который выполняет побитовое копирование (shallow copy).
Подводные камни:
Пример:
class StringWrapper { char* data; public: StringWrapper(const char* str) { data = new char[strlen(str) + 1]; strcpy(data, str); } // Ошибка: shallow copy StringWrapper(const StringWrapper& other) : data(other.data) {} ~StringWrapper() { delete [] data; } }; void foo(StringWrapper s) { // ... } int main() { StringWrapper s1("hello"); foo(s1); // UB!!! return 0; }
"Что будет, если определить конструктор копирования в классе с указателем так:
MyClass(const MyClass &other) : data(other.data) {}? Какие последствия это вызывает?"
Верный ответ: Такой конструктор копирования создаст объект с указателем на ту же область памяти, что и у копируемого. При уничтожении двух объектов память будет освобождена дважды (double free), что приводит к неопределённому поведению. Следует реализовывать deep copy:
MyClass(const MyClass &other) { data = new int(*other.data); }
История
В большом серверном проекте использовали контейнеры из объектов с "сырым" массивом внутри и стандартным копирующим конструктором (shallow copy). При передаче объектов по значению возникал double free и краши приложения, улавливаемые только в продакшне.
История
В старой С++ библиотеке для работы с изображениями копирующий конструктор не копировал буфер графики, что приводило к изменению одной копии изображения при изменении другой и неожиданным багам в интерфейсе.
История
При возврате объекта по значению из функции в одной из внутренних систем хранения паролей данные очищались при уничтожении временного объекта (shallow copy), в результате чего реальный объект хранил обнулённый указатель, а утечка выявилась случайно в процессе аудита безопасности.