gator
gator

Reputation: 3523

How do you perform a deep copy on an object? How do you make a copy constructor?

#include <iostream>

class Piece {
    public:
        virtual char get()=0;
        virtual ~Piece() {};
};

class One : public Piece {
    public:
        char get() { return '1'; }
};

class Two : public Piece {
    public:
        char get() { return '2'; }
};

class Tile {
    private:
        Piece* occ;
        bool prs;
    public:
        Tile() { prs = false; }
        void setOcc(Piece* p) { prs = true; occ = p; }
        Piece& getOcc() { return *occ; }
        bool getPrs() { return prs; }
        void explicitDest() { if (prs) { delete occ; prs = false; } }
};

class Board {
    private:
        Tile tiles[2][2];
    public:
        Board() { 
            tiles[0][0].setOcc(new One());
            tiles[0][1].setOcc(new Two());
            tiles[1][1].setOcc(new One());
        }
        Tile getTile(int c, int r) { return tiles[c][r]; }
        void move(Board* b, int c1, int r1, int c2, int r2) { 
            switch(b->tiles[c1][r1].getOcc().get()) {
                case '1': b->tiles[c2][r2].setOcc(new One()); break;
                case '2': b->tiles[c2][r2].setOcc(new Two()); break;
            }
            b->tiles[c1][r1].explicitDest();
        }
        void print() {
            for (int i = 0; i < 2; i++) {
                for (int j = 0; j < 2; j++) {
                    if (tiles[j][i].getPrs()) {
                        std::cout << tiles[j][i].getOcc().get() << " ";
                    } else {
                        std::cout << "- ";
                    }
                }
                std::cout << "\n";
            }
            std::cout << "\n";
        }
        Board* copyBoard() { return new Board(*this); }
};

int main()
{
    Board* oldBoard = new Board();
    std::cout << "Old board: \n";
    oldBoard->print();
    Board* newBoard = oldBoard->copyBoard();
    std::cout << "New board: \n";
    newBoard->print();
    newBoard->move(newBoard, 0, 0, 1, 1);
    std::cout << "Old board after move: \n";
    oldBoard->print();
    std::cout << "New board after move: \n";
    newBoard->print();
    delete[] newBoard;
}

This is an MRE to illustrate the methodology I've been using to do deep copies. It doesn't exactly work and is just to visualize how I've been doing things.

Using this example, is my method for deep copying strong? If not, what methods are available with a C++03 constraint to ensure a copy (and changes to the copy) do not reflect upon the original it's based upon?

In the code, I defined explicitDest() which is my way of explicitly (and only explicitly) calling the destructor as I need certain behavior only sometimes. Just in case people asked.

I'm not terribly familiar with copying, copy constructors, or abstract classes/methods if the code wasn't obvious.

Upvotes: 0

Views: 134

Answers (1)

Ted Lyngmo
Ted Lyngmo

Reputation: 117298

You should implement copy constructors and copy assignment operators and take care when using new. You need one delete for each new - unless you surrender the pointer returned by new to a smart pointer. In C++03 you have std::auto_ptr that can be used to manage the memory resources for you.

Here's an example with comments inline:

#include <iostream>
#include <memory>    // std::auto_ptr
#include <algorithm> // std::swap (<algorithm> in c++03, <utility> in >= c++11) 

class Piece {
public:
    // A virtual destructor to support deleting via base class pointer:
    virtual ~Piece() {}

    // You can't make constructors virtual, so add a clone()
    // function for copy constuction through a base class pointer
    virtual std::auto_ptr<Piece> clone() const = 0;

    // renamed get() into symbol()
    virtual char symbol() const = 0;
};

class One : public Piece {
public:
    // Use the implicit copy constructor for One and return a (smart) pointer
    // to the base class.
    std::auto_ptr<Piece> clone() const {
        return std::auto_ptr<Piece>(new One(*this));
    }
    char symbol() const { return '1'; }
};

class Two : public Piece {
public:
    std::auto_ptr<Piece> clone() const {
        return std::auto_ptr<Piece>(new Two(*this));
    }
    char symbol() const { return '2'; }
};

class Tile {
private:
    std::auto_ptr<Piece> occ;  // this now handles delete for you

public:
    Tile() : occ(NULL) {}      // default constructor
    Tile(Piece* p) : occ(p) {} // put pointer in auto_ptr

    // copy constructor, use the clone() function and conversion
    // to bool operator below. If "o" doesn't have a Piece, initialize occ
    // with an default constructed, empty, auto_ptr<Piece>.
    Tile(const Tile& o) : occ(o ? o.occ->clone() : std::auto_ptr<Piece>()) {}
    //                        ^
    //                        |
    //                        +--- conversion to bool in use

    // copy assignment operator
    Tile& operator=(const Tile& o) {
        Tile tmp(o);   // use the copy constructor above
        occ = tmp.occ; // steal pointer from tmp
        return *this;
    }

    // converting assignment operator
    Tile& operator=(Piece* p) {
        // delete the old pointer and replace it with p:
        occ.reset(p);
        return *this;
    }

    // Conversion to bool operator using std::auto_ptr's built in get()
    // to tell us if we have a Piece or not.
    operator bool() const { return occ.get() != NULL; }

    // Add a symbol() function to hide the logic to determine if this Tile
    // has a Piece or not.
    char symbol() const {
        // Check if there is a Piece in this Tile using the conversion
        // to bool operator here too:
        if(*this)
            return occ->symbol();
        else
            return '-'; // no Piece here
    }
};

// add support to stream a Tile to an ostream
std::ostream& operator<<(std::ostream& os, const Tile& t) {
    return os << t.symbol();
}

class Board {
private:
    Tile tiles[2][2];

public:
    Board() {
        // using the added operator() further down
        (*this)(0,0) = new One;
        (*this)(0,1) = new Two;
        (*this)(1,1) = new One;
    }

    // Note that cols and rows in arrays are usually seen as reversed.
    // tiles[2][2]               usually means:
    // tiles[<rows>=2][<cols>=2]

    // getTile() replacements - the interface here is still (col, row)
    // but it accesses the tiles[][] using the common form (row, col)
    Tile& operator()(int c, int r) { return tiles[r][c]; }
    Tile const& operator()(int c, int r) const { return tiles[r][c]; }

    // moving by swapping tiles
    void move(int c1, int r1, int c2, int r2) {
        // using operator() and the standard function std::swap
        std::swap((*this)(c1, r1), (*this)(c2, r2));
    }
};

// Add a stream operator to not have to call print() explicitly when streaming
std::ostream& operator<<(std::ostream& os, const Board& b) {
    for(int r = 0; r < 2; r++) {
        for(int c = 0; c < 2; c++) {
            // Use "Board::operator() const" and stream support for returned
            // Tile.
            os << b(c, r);
        }
        os << '\n';
    }
    os << '\n';
    return os;
}

int main() {
    // no need to "new" anything:

    Board oldBoard;
    Board newBoard(oldBoard); // use copy constructor

    // use streaming operators
    std::cout << "Old board: \n" << oldBoard;
    std::cout << "New board: \n" << newBoard;

    // using the getTile() replacement, Board::operator():
    std::cout << "New board @ tile 1,0: " << newBoard(1, 0) << " before move\n";

    newBoard.move(0, 0, 1, 0);
    std::cout << "New board @ tile 1,0: " << newBoard(1, 0) << " after move\n\n";

    std::cout << "New board after move:\n" << newBoard;

    newBoard = oldBoard; // copy assignment operator
    std::cout << "New board after reinit:\n" << newBoard;
}

Be aware of that there are many things inside example that would be done in a slightly different (more efficient) way in C++11 and later where std::unique_ptr, move semantics and extended initializer lists were added.

Upvotes: 1

Related Questions