liveBook · Manning

Member-wise copy

As explained in 100 C++ Mistakes and How to Avoid Them

Member-wise copy is the operation of copying an object's individual data members from a source object to a target object.

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

How 100 C++ Mistakes and How to Avoid Them covers it

How deep it goes
Distinguishes construction from assignment and demonstrates explicit copying, but stops short of a general resource-management redesign.
Examples
Uses InputReader arrays, file names, segmentation faults, and Student assignment that initially omits the Person state.
What you can do after
Diagnose/debug with it by identifying shallow resource copies and missing base-class assignment, then implement explicit copying.
How it connects
Connects member-wise copy with copy constructors, copy assignment operators, shallow copy, dynamic resources, and base classes.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 C++ Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.