views:

363

answers:

6

I am relatively new to C++ programming, but am a C programmer of 10 years so am more comfortable with pointers to objects than I am with references to objects.

I'm writing a Solitaire game - is this design unsafe? Is there a better way?

Anyway, I have a class SolitaireGame:

class SolitaireGame:
{
    public:
        SolitaireGame( int numsuits = 1 );
    private:
        Deck * _deck;
        vector<Card> _shoe;
};

The Deck is defined thus:

class Deck:
{
public:
 Deck::Deck( vector<Card>& shoe );
 ~Deck();
 int DealsLeft() const { return deals_left; }
 Card * PullCard();
private:
 int deals_left;
 int num_each_deal;
 deque<Card *> _cards;
};

The Deck constructor, takes a reference to a vector of Card objects ( the shoe, normally 104 cards ) and pushes a pointer to each card onto it's own deque of pointers.

Deck::Deck( vector<Card>& shoe )
{
    vector<Card>::iterator iter = shoe.begin();

    while( iter != shoe.end() )
    {
        _cards.push_front( &(*iter) );
        iter++;
    }
}

}

The shoe is created in the SolitaireGame constructor. Once this vector of dynamically created Card objects has been created - I then pass a reference to this vector to the constructor.

SolitaireGame::SolitaireGame( int numsuits ):_numsuits(numsuits ) 
{
    Card * c;
    vector<Card> _shoe;

    for( int i = 0; i < NUM_CARDS_IN_SHOE; i++ )
    {
        c = new Card();
        _shoe.push_back( *c );
    }

    _deck = new Deck( _shoe );
}

My idea was that the shoe would be the container for the actual memory for the Card objects and the Deck and Columns just handle pointers to those Card objects.

+4  A: 

I think there're two mistakes:

  1. When you do _shoe.push_back( *c );, you're creating a copy of the Card object, so the memory reserved to c will never be freed. Btw, you should always check that for each new exists a complementary delete. Where is your delete?
  2. In your Deck constructor you're saving pointers to objects that reside in the stack (vector<Card> _shoe;), so as soon as the SolitaireGame constructor ends, they will be deleted and your pointers will be invalid. EDIT: I see you've got another _shoe in your class, so it's not necessary to declare another _shoe local variable, in fact just by not declaring it you will solve this issue.

I hope this helps you a bit.

David Alfonso
+15  A: 

Just taking this snippet of code, you leak dynamically created cards.

Card * c;
vector<Card> _shoe;

for( int i = 0; i < NUM_CARDS_IN_SHOE; i++ )
{
    c = new Card();
    _shoe.push_back( *c );
}

_shoe.push_back( *c ) adds a copy of the Card object pointed to by c to the vector of Cards. You then fail to delete the original Card as created in the line before.

Allocating a vector of NUM_CARDS_IN_SHOE Cards can much more simply be achieved like this:

std::vector<Card> _shoe( NUM_CARDS_IN_SHOE );

Looking at your card structure, it looks like you have (or nearly have) strict ownership between objects so I don't think that you need to dynamically create your Cards.

Note that your local variable _shoe is shadowing the class variable _shoe. This probably isn't what you want as the local _shoe which you pass to the Deck constructor will go out of scope at the end of the constructor.

If you reorder you variables in SolitaireGame, you can probably do something like this:

class SolitaireGame:
{
public:
    SolitaireGame( int numsuits = 1 );
private:
    vector<Card> _shoe;
    Deck _deck;
};

SolitaireGame::SolitaireGame( int numsuits )
    : _shoe(NUM_CARDS_IN_SHOE)
    , _deck(_shoe)
{
}

I've changed _deck from being a pointer. I'm using the fact that member variables are constructed in the order declared in the class definition, so _shoe will be fully constructed before it is passed as a reference to the constructor for _deck. The advantage of this is that I have eliminated the need to dynamically allocate _deck. With no uses of new, I know that I can't have any missed calls to delete as nothing needs to be deallocated explicitly.

You are right that you can store pointers to the Cards in _shoe in your _deck without any memory management issues, but note that you must not add or remove any of the Cards in the _shoe during the lifetime of the game otherwise you will invalidate all of the pointers in _deck.

Charles Bailey
+1  A: 

Since such a game object always has its own deck you should consider making the Deck object a real member inside SolitairGame -- not just a pointer. This will make life-time management of the deck object much simpler. For example, you won't need a custom destructor anymore. Keep in mind that STL containers contain copies. If you write something like

myvector.push_back(*(new foo));

you have a memory leak.

In addition, storing pointers to elements of a vector is dangerous because the pointers (or iterators in general) might become invalid. For a vector this is the case when it needs to grow. An alternative is std::list which keeps iterators valid after insertion, deletion, etc.

Also, keep in mind that in C++ structs and classes usually get implicit copy constructors and assignment operators. Honor the rule of three. Either disallow copying and assignment or make sure that resources (including dynamically allocated memory) is properly managed.

sellibitze
+2  A: 

Initial thoughts:

  1. In class SolitaireGame, you declare _shoe as:

    vector<Card> _shoe;

    but in the constructor you push heap objects on to it like this:

    c = new Card();

    _shoe.push_back( *c );

    So, you need to declare it like this:

    vector<Card*> _shoe;

  2. You don't initialise variables in constructors, such as deals_left and num_each_deal in class Deck. I'll assume you left it out to not clutter up the code, but it's a good idea.

  3. Class SolitaireGame creates and owns the Deck objects. It also has a Deck with pointers to SolitaireGame's Card objects. The ownership here is unclear - who deleted them? While having pointers to objects in multiple containers will work, it can make debugging more difficult, as there's scope for multiple deletion, using after it's been deleted, leaks etc. Perhaps the design could be simplified. Perhaps have Deck own the Card objects initially, and when they're removed, they get put into the vector in SolitaireGame, and don't exist in both at the same time.

  4. In the constructor for SolitaireGame, you declare another vector of cards, which shadows the one declare in the class declaration. When you push the Card objects onto it, they'll not get pushed to the correct vector, which will go out of scope at the end of the constructor, and your class member will be empty. Just get rid of it from the constructor.

Anyway, I need a cup of tea. After that I'll take another look and see if I can suggest anything else.

pxb
Thank you for the reply, pxb. Yes you are right, I left out initialisation of `deals_left` in the initial post, so as not to clutter up code. Re. point 1, the declaration `vector<Card*> _shoe` declares a vector of pointers to objects, yet what I am doing is dereferencing the pointer before I do the `push_back` via `_shoe.push_back( *c )`. I thought that `c` was a pointer that I was dereferencing so I'm actually pushing the object itself. Or perhaps I have misunderstood?
BeeBand
yes, doing *c will dereference the pointer, so what'll you'll push back to the vector will be a copy of the object (ie. a new object and the copy constructor will be invoked to copy internal data).Is this what you intended to do? It could be quite expensive and you'll get duplicates of each Card object, not pointers to the same one. If the vector grows internally then memory will be reallocated and each of your objects will be copied again. This might be wanted you intended, but seems unusual to me, and could lead to subtle bugs/leaks.
pxb
+1  A: 

This program will leak memory , Want to find out why ? or how ?

push_back

Do remember this call do not insert your supplied element , But creates a copy of it for own use. Read this for detail

So

    Card *c = new Card(); // This is on heap , Need explicit release by user

If you change it to 

    Card c; // This is on stack, will be release with stack unwinding

Copy below program and execute it, {I simply added logging}, try with both option, above

#include<iostream>
#include <vector>
#include <deque>

using namespace std;

const int NUM_CARDS_IN_SHOE=120;

class Card
{
public:
    Card()
    {
     ++ctr;
     cout<<"C'tor callend: "<<ctr<<" , time"<<endl;
    }
    ~Card()
    {
     ++dtr;
     cout<<"D'tor called"<<dtr<<" , time, num still to release: "<<((ctr+cpy)-dtr)<<endl;
    }
    Card& operator=(const Card & rObj)
    {
     return *this;
    }

    Card (const Card& rObj)
    {
     ++cpy;
     cout<<"Cpy'tor called"<<cpy<<endl;
    }
private:


    static int ctr,dtr,rest,cpy;
};
int Card::ctr;
int Card::dtr;
int Card::rest;
int Card::cpy;
class Deck
{
public:
 Deck::Deck( vector<Card>& shoe );
 ~Deck();
 int DealsLeft() const { return deals_left; }
 Card * PullCard();
private:
 int deals_left;
 int num_each_deal;
 std::deque<Card *> _cards;
};

Deck::Deck( vector<Card>& shoe )
{
    vector<Card>::iterator iter = shoe.begin();

    while( iter != shoe.end() )
    {
        _cards.push_front( &(*iter) );
       iter++;
    }
}
class SolitaireGame
{
    public:
        SolitaireGame( int numsuits = 1 );
    private:
        Deck * _deck;
     std::vector<Card> _shoe;
};





SolitaireGame::SolitaireGame( int numsuits )
{
    Card * c;
    vector<Card> _shoe;

    for( int i = 0; i < numsuits; i++ )
    {
        c = new Card();
        _shoe.push_back( *c );
    }

    _deck = new Deck( _shoe );
}



int main() 

{
    {
    SolitaireGame obj(10);
    }
    int a;
    cin>>a;
    return 0;
}
sat
Thank you for your reply SSS. This is a great demonstration, on how `push_back` creates copies ( I had no idea before posting this question! ) - however, going off on a tangent a bit, I'm now completely confused as to why the copy constructor is invoked 35 times and not 10! I am currently investigating...
BeeBand
@BeeBand , I have not tested every thing properly , My regrets if i have commited any thing wrong, My main aim was to make push_back concept clear to you
sat
+2  A: 

I don't think the new keyword should appear anywhere in the code of these classes, and I don't see why you'd go through the trouble to share cards through pointers. Storing addresses of items held in a vector is recipe for disaster - you need to guarantee that there will be no modifications to the vector after you take the addresses, as it tends to move things around in memory without telling you.

Assuming a Card object doesn't store anything besides one or two ints, it would be a lot simpler to work with copies and values.

_deck = new Deck( _shoe );

Again, I don't see a slightest reason to increase complexity of the program by allocating an object containing two ints and a deque dynamically.

If you are worried about cost of copying some of the larger classes you have (which I would estimate has zero impact on perceived performance here), then simply don't copy them, and pass them around by const reference (if you don't need to mutate the instance), or non-const reference/pointer otherwise.

UncleBens