Member-wise copy copies each instance variable from a source object into a target object. Compiler-generated copy operations use this behavior by default, which can be correct for value-like members but dangerous for dynamic resources. section 8.5
Treat member-wise copy differently depending on the target and the data being copied:
- A copy constructor initializes a new, uninitialized object from an existing object. section 8.5
- A copy assignment operator overwrites an already initialized target object. section 8.5
- Pointer members, memory, handles, connections, and sockets can be copied shallowly, leaving objects sharing a resource and potentially causing invalid access. section 8.5
- Derived-class assignment must copy the base-class state before copying derived members. section 8.7
The InputReader failure
An InputReader stores filenames in a dynamically allocated array. Default member-wise copy makes ir1, ir2, and ir3 point at the same array; adding filenames corrupts shared state, and destruction can end in a segmentation fault. A custom copy constructor allocates separate storage and copies each filename, while assignment copies into the existing storage. section 8.5
Listing 8.8 Resources handled by default copy constructor and copy assignment operator
class InputReader {
private:
std::string* filenames; #1
int count;
public:
InputReader() : filenames(new std::string[5]), count(0) {}
~InputReader() { delete[] filenames; }
void addFileName(const std::string& n) {
if (count == 5)
throw std::runtime_error("too many files specified");
filenames[count++] = n;
}
int getCount() const { return count; }
const std::string& operator[](int index) {
return filenames[index];
}
};
int main() {
InputReader ir1;
ir1.addFileName("statistics.txt");
InputReader ir2(ir1); #2
ir2.addFileName("numbers.txt");
InputReader ir3 = ir2; #3
ir3.addFileName("categories.txt");
ir1.addFileName("questions.txt");
for (int i = 0; i < ir1.getCount(); ++i)
std::cout << ir1[i] << '\n';
for (int i = 0; i < ir2.getCount(); ++i)
std::cout << ir2[i] << '\n';
for (int i = 0; i < ir3.getCount(); ++i)
std::cout << ir3[i] << '\n';
return 0;
}
Inspect the resource-owning InputReader example to see how compiler-generated member-wise copy creates shallow aliases. section 8.5
Listing 8.9 A properly written copy constructor and assignment operator
class InputReader {
private:
std::string* filenames;
int count;
public:
InputReader() : filenames(new std::string[5]), count(0) {}
~InputReader() { delete[] filenames; }
InputReader(const InputReader& r) : filenames(new std::string[5]),
count(r.count) { #1
for (int i = 0; i < r.count; ++i)
filenames[i] = r.filenames[i];
}
InputReader& operator=(const InputReader& r) { #2
for (int i = 0; i < r.count; ++i)
filenames[i] = r.filenames[i];
count = r.count;
return *this; }
void addFileName(const std::string& n) {
if (count == 5)
throw std::runtime_error("too many files specified");
filenames[count++] = n;
}
int getCount() const { return count; }
const std::string& operator[](int index) {
return filenames[index];
}
};
int main() {
InputReader ir1;
ir1.addFileName("statistics.txt");
InputReader ir2(ir1); #3
ir2.addFileName("numbers.txt");
InputReader ir3 = ir2; #4
ir3.addFileName("categories.txt");
ir1.addFileName("questions.txt");
for (int i = 0; i < ir1.getCount(); ++i)
std::cout << ir1[i] << '\n';
for (int i = 0; i < ir2.getCount(); ++i)
std::cout << ir2[i] << '\n';
for (int i = 0; i < ir3.getCount(); ++i)
std::cout << ir3[i] << '\n';
return 0;
}
Compare the explicit copy constructor and assignment operator, which allocate or reuse storage and copy the contained filenames. section 8.5
