How to pass a unique_ptr to set the member variabl

2019-08-17 08:38发布

问题:

I'm writing a chess application. The Board class contains an array of Square, each of which can hold a Piece. Type-specific classes (Pawn, Rook, etc.) inherit from Piece.

In order to accomplish this, Square has a member variable which points to a particular Piece (which is occupying that Square).

The trouble I'm having is that when I try to set up the Board, I am unable to assign the unique_ptr that I've created to the member variable of the Square.

Here's the general stream of function calls:

void Board::setBoard()
{
    // White Pawn Placement
    std::unique_ptr<Piece> wp1 = std::make_unique<Pawn>(PAWN, WHITE);
    Board::setPiece("a2", wp1);
}

Pawn::Pawn(Type t, Color c) : Piece(t, c) {}

void Board::setPiece(const std::string &coords, std::unique_ptr<Piece> piece)
{
    squareMap[coords].setPiece(piece);
}

void Square::setPiece(std::unique_ptr<Piece> piece)
{
    Square::piece = std::move(piece);
}

I receive the following error when I attempt to compile at the line holding Board::setPiece("a2", wp1);

error: call to implicitly-deleted copy constructor of 'std::unique_ptr<Piece>'

which is, needless to say, a bit of a mouthful.

There is some good documentation online about inheritance, abstract classes, how to use the unique_ptr, etc., but I've been unable to figure out how all of those things fit together.

So, the question:

How can I create an object, assign it to a unique_ptr, and then use that unique_ptr to set the member variable of another object?

Here are the header files for each class, in case that is illuminating. And please forgive the length of my question. I've made it as short as I can.

Board.hpp

class Board {
public:
    Board();
    void printBoard();
    Piece getPiece(const std::string &coords);
    void setPiece(const std::string &coords, std::unique_ptr<Piece> piece);
    ~Board();
    ...
};

Square.hpp

class Square
{
public:
    void setPiece(std::unique_ptr<Piece> piece);
    Piece* getPiece() const;
protected:
    std::unique_ptr<Piece> piece;
    ...
};

Piece.hpp

class Piece
{
public:
    Piece();
    Piece(Type, Color);
    virtual bool movePiece() = 0; // abstract class
protected:
    Color color;
    Type type;
    bool moved;
    ...
};

Pawn.hpp

class Pawn : public Piece
{
public:
    Pawn(Type t, Color c);
    bool movePiece() override;
};

回答1:

The problem is you are passing around std::unique_ptr objects by value without the use of std::move(), so you are trying to copy them instead of move them. std::unique_ptr cannot be copied from, only moved from, otherwise single-ownership semantics would be broken. That is what makes std::unique_ptr "unique" - only 1 std::unique_ptr at a time can refer to a given object in memory.

Try something more like this instead:

class Pawn : public Piece
{
public:
    Pawn(Color c);
    ...
};

Pawn::Pawn(Color c) : Piece(PAWN, c) {}

class Square
{
public:
    ...

    // for accessing the current Piece without moving it around the Board
    // (for printing, drawing, etc)...
    const Piece* getPiece() const;

    // for moving Pieces around the Board...
    std::unique_ptr<Piece> setPiece(std::unique_ptr<Piece> piece);

    ...

protected:
    std::unique_ptr<Piece> piece;
    ...
};

const Piece* Square::getPiece() const
{
    return piece.get();
}

std::unique_ptr<Piece> Square::setPiece(std::unique_ptr<Piece> piece)
{
    std::unique_ptr<Piece> old = std::move(this->piece);
    this->piece = std::move(piece);
    return std::move(old);
}

class Board {
public:
    ...

    // for accessing a current Piece without moving it around the Board
    // (for printing, drawing, etc)...
    const Piece* getPiece(const std::string &coords) const;

    // for moving Pieces around the Board...
    std::unique_ptr<Piece> setPiece(const std::string &coords, std::unique_ptr<Piece> piece);

    ...

protected:
    std::map<std::string, Square> squareMap;
    ...
};

void Board::setBoard()
{
    ...

    // White Pawn Placement
    std::unique_ptr<Piece> wp1 = std::make_unique<Pawn>(WHITE);
    setPiece("a2", std::move(wp1));

    // or simply:
    //setPiece("a2", std::make_unique<Pawn>(WHITE));

    ...
}

const Piece* Board::getPiece(const std::string &coords) const
{
    auto iter = squareMap.find(coords);
    return (iter != squareMap.end())
        ? iter->second.getPiece()
        : nullptr;
}

std::unique_ptr<Piece> Board::setPiece(const std::string &coords, std::unique_ptr<Piece> piece)
{
    return squareMap[coords].setPiece(std::move(piece));
}

This allows Square to maintain ownership of whatever Piece is currently assigned to it, and that ownership is transferred only when a new Piece is assigned (for instance, to move a captured Piece to another list, to be reclaimed when a Pawn gets promoted).