When an object is passed by value to a function in C++, a copy of the object is created using the copy constructor. If a user-defined copy constructor is defined in the class, it is called to initialize the temporary argument object of the function. If not defined, the default compiler-generated constructor is used, which performs a bitwise copy (shallow copy).
Pitfalls:
Example:
class StringWrapper { char* data; public: StringWrapper(const char* str) { data = new char[strlen(str) + 1]; strcpy(data, str); } // Error: 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; }
"What will happen if the copy constructor is defined in a class with a pointer like this:
MyClass(const MyClass &other) : data(other.data) {}? What consequences does this cause?"
Correct answer: Such a copy constructor will create an object with a pointer to the same memory area as the copied one. When both objects are destroyed, the memory will be freed twice (double free), which leads to undefined behavior. Deep copy should be implemented:
MyClass(const MyClass &other) { data = new int(*other.data); }
Story
In a large server project, containers of objects with a "raw" array inside and a standard copy constructor (shallow copy) were used. When passing objects by value, double free occurred and application crashes were caught only in production.
Story
In an old C++ library for working with images, the copy constructor did not copy the graphics buffer, which led to changing one copy of the image when changing another and unexpected bugs in the interface.
Story
When returning an object by value from a function in one of the internal password storage systems, the data was cleared when the temporary object was destroyed (shallow copy), resulting in the actual object holding a null pointer, and the leak was discovered accidentally during a security audit.