Search code examples
c++stackshared-ptrsmart-pointersstdvector

std::vector of vectors deallocates its shared_ptr content (incorrect usage of stack solid objects?)


Vector of vectors of shared_ptr deallocates its Piece instances. I created it in my Board class like this:

std::vector < std::vector < std::shared_ptr <Piece> > > board; 

But it deallocates each time the inner cycle leaves the scope:

Board::board with capacity 10 size: 10
Piece born 0x7faedbc1dc78
Row 0 Column 0 piece0x7faedbc1dc78
Piece born 0x7faede303fd8
Row 1 Column 0 piece0x7faede303fd8
Piece born 0x7faede30d2a8
Row 2 Column 0 piece0x7faede30d2a8
Piece born 0x7faede30d248
Row 3 Column 0 piece0x7faede30d248
Piece born 0x7faede30d348
Row 4 Column 0 piece0x7faede30d348
Piece born 0x7faede30d368
Row 5 Column 0 piece0x7faede30d368
Piece born 0x7faede30d4c8
Row 6 Column 0 piece0x7faede30d4c8
Piece born 0x7faede30d4e8
Row 7 Column 0 piece0x7faede30d4e8
Piece born 0x7faede30d508
Row 8 Column 0 piece0x7faede30d508
Piece born 0x7faede30d528
Row 9 Column 0 piece0x7faede30d528
Piece death 0x7faede30d528
Piece death 0x7faede30d508
Piece death 0x7faede30d4e8
Piece death 0x7faede30d4c8
Piece death 0x7faede30d368
Piece death 0x7faede30d348
Piece death 0x7faede30d248
Piece death 0x7faede30d2a8
Piece death 0x7faede303fd8
Piece death 0x7faedbc1dc78
Piece born 0x7faede303fd8
Row 0 Column 1 piece0x7faede303fd8
Piece born 0x7faede30d2a8
Row 1 Column 1 piece0x7faede30d2a8
Piece born 0x7faede30d248
Row 2 Column 1 piece0x7faede30d248
Piece born 0x7faedbe7db98
Row 3 Column 1 piece0x7faedbe7db98
Piece born 0x7faedbe8f6a8
Row 4 Column 1 piece0x7faedbe8f6a8
Piece born 0x7faedbe8f868
Row 5 Column 1 piece0x7faedbe8f868
Piece born 0x7faedbe8f888
Row 6 Column 1 piece0x7faedbe8f888
Piece born 0x7faedbe8f8a8
Row 7 Column 1 piece0x7faedbe8f8a8
Piece born 0x7faedbe8f8c8
Row 8 Column 1 piece0x7faedbe8f8c8
Piece born 0x7faedbe8f8e8
Row 9 Column 1 piece0x7faedbe8f8e8
Piece death 0x7faedbe8f8e8
Piece death 0x7faedbe8f8c8
Piece death 0x7faedbe8f8a8
Piece death 0x7faedbe8f888
Piece death 0x7faedbe8f868
Piece death 0x7faedbe8f6a8
Piece death 0x7faedbe7db98
Piece death 0x7faede30d248
Piece death 0x7faede30d2a8
Piece death 0x7faede303fd8
Piece born 0x7faedbe7db98
Row 0 Column 2 piece0x7faedbe7db98 ...

Heres the Board class code:

#include "Board.h"
#include <iostream>

using namespace std;

Board::Board(int width_, int height_) : width(width_), height(height_), board(height)
{
    cout << "Board::board with capacity " << board.capacity() << " size: " << board.size() << endl;
//    board = vector< vector < shared_ptr < Piece >>> (height) ;

    for (int i=0; i<height; i++)
    {
        vector < shared_ptr < Piece > > row(width);
        board[i] = row;

        for (int j=0;j<width;j++) {
            shared_ptr< Piece > piece = make_shared<Piece>();
            row[j] = piece;
            cout << "Row " << j << " Column " << i << " piece" << &*piece << endl;
        }
    }
}

Relevant Piece code (basically it just squacks)

class Piece {
public:
    Piece() { std::cout << "Piece born " << this << std::endl;}
    ~Piece() { std::cout << "Piece death " << this << std::endl;}
    Piece(const Piece &rhs) {
        std::cout << "Piece copy " << this << " from " << &rhs << std::endl;
    }
    Piece & operator=(const Piece & rhs) {
        std::cout << "Piece assigned " << this << " from " << &rhs << std::endl;
        return *this;
    }
};

No copy or assign operators are invoked. So I believe its NOT due to vector self-reallocation because I pre-allocated outermost vector in Board's constructor (as well as local vector allocated to "width" in inner cycle)


Solution

  • board[i] = row;
    

    This copies the contents of the row vector at the time of the assignment into board[i]. Then, you proceed to fill the local row vector, but do nothing more with it.

    Move the assignment until after loop:

    for (int i=0; i<height; i++)
    {
        vector < shared_ptr < Piece > > row(width);
    
        for (int j=0;j<width;j++) {
            shared_ptr< Piece > piece = make_shared<Piece>();
            row[j] = piece;
            cout << "Row " << j << " Column " << i << " piece" << &*piece << endl;
        }
        board[i] = std::move(row);
    }
    

    Alternatively, to get rid of the assignment altogether, you could introduce row as simply an alias:

    for (int i=0; i<height; i++)
    {
        auto &row = board[i];
        row.resize(width);
    
        for (int j=0;j<width;j++) {
            shared_ptr< Piece > piece = make_shared<Piece>();
            row[j] = piece;
            cout << "Row " << j << " Column " << i << " piece" << &*piece << endl;
        }
    }
    

    If you want to streamline the code even more, you can do this (I would even say it's clearer):

    for (int i=0; i<height; i++)
    {
        auto &row = board[i];
    
        for (int j=0;j<width;j++) {
            row.emplace_back(make_shared<Piece>());
            cout << "Row " << j << " Column " << i << " piece" << row.back().get() << endl;
        }
    }
    

    Notice that std::shared_ptr::get() returns the raw pointer, and I personally find it preferable to &*.