I have created some C++ classes to model a Solitaire game as a learning exercise.
I have classes for the SolitaireGame, a CardStack (one of the 10 piles of cards on the board ) and a Card. My current model states that the SolitaireGame owns a vector of 104 Card objects - which I call the 'shoe'. The SolitaireGame also keeps track of 10 CardStacks which are essentially deque's of addresses of Card object开发者_JAVA技巧s stored in the shoe. The Deck and Hand inherit from CardStack. I pass the cards from Deck, to Hand to Cascade by means of pointers to original objects stored in the Shoe.
According to a number of answers I received to this question, I should not be passing the Card's around by pointers, but should be using const references. The reason being that objects stored in vectors can have their addresses moved about, so storing their addresses anywhere is a no-no. I recently started looking at boost::sharedptr. What do people think about using shared_ptr to Card here?
Here are simplified versions of the classes:
class SolitaireGame
{
public:
SolitaireGame::SolitaireGame( int numsuits );
private:
vector<Card> _shoe;
Deck _deck;
Hand _hand;
CardStack _cols[NUM_COLUMNS];
int _numsuits;
GameState gamestate;
};
class CardStack
{
public:
CardStack(){ cout << "CardStack constructor" << endl; }
CardStack( const CardStack& );
CardStack( const deque<Card *> &d );
~CardStack(){ }
virtual Card * PullCard( Face f );
virtual void PushCard( Card * c );
Card * CardAt( int i ) const;
Card * Top() const;
deque<Card *>::iterator Begin() { return _cards.begin(); }
deque<Card *>::iterator End() { return _cards.end(); }
int Size() const;
CardStack& operator=( const CardStack& rhs );
friend std::ostream& operator<<(std::ostream &os, const CardStack &obj);
private:
deque<Card *> _cards;
};
The reason being that objects stored in vectors can have their addresses moved about, so storing their addresses anywhere is a no-no.
Storing (const) references is just as bad as storing pointers for the same reason. If the size of the vector does not change as long as other objects hold pointers to the objects therein, you should be safe.
When programming in C++, you should always decide who “owns” an object, e.g. who is responsible to delete it when it is no longer needed. If there is no natural object owner, you could resort to smart pointers like boost::shared_ptr
that use reference counting or garbage collection to manage the object's lifetime.
In your case, it is pretty obvious that the SolitaryGame
instance owns all cards. Moreover, the number of cards in the game is fixed. Therefore you can easily pass pointers of your cards to objects that are dependent of the game instance.
Once the game is deleted, all cards will get deleted and remaining pointers will be invalid, but at this time, other objects holding card pointers should get deleted, too.
Yes if you are taking the address of the elements of your
vector<Card> _shoe;
and placing them into your
deque<Card *> _cards;
There could definitely be a problem, like you describe. Your vector could reallocate, making the address of the Card elements of vector no longer valid.
Passing references (const or otherwise) to the contents of your vector will have the same problems as passing around pointers. In C++ a reference is really a thinly-veiled pointer. The only difference from a pointer being how its used (as an alias) the fact that it can't be "unseated", that is made NULL, and the fact that it's not distinguishable from the aliased type (you can't have a vector of Card references). A reference doesn't have any special reference counting or anything you get with other garbage collected languages. So when your vector reallocates, if anyone holds a reference to any of the cards in the decks, those references will fail just as easily as a pointer would.
Replacing your vector with a vector of boost::shared_ptr's of Cards could resolve your problems. A boost::shared_ptr is reference counted. This means it keeps track of how many referrers exist to the underlying object. And your vector would be a vector of shared_ptrs, not of the object itself. So when the vector get reallocated, you're just adding a new referrer back to the underlying object temporarily during reallocation, and then the vector is replacing the shared_ptr with the shared_ptr living in reallocated space. The underlying object doesn't move.
I would take this a step further and recommend not giving everyone a shared_ptr. Pass around boost::weak_ptr's to the non-owners. A boost::weak_ptr is a weak reference to the underlying data. A weak_ptr gives someone a handle to obtain a shared_ptr when needed. It doesn't participate in the reference count of the underlying data. So you can check, first, if the underlying data was deleted by the owner. Then if it wasn't deleted, obtain a shared_ptr (temporarilly participating in the referrer count) and perform the needed action.
You can't store references in a container, so if you want shared access, you can only use pointers. shared_ptr
would seem somewhat meaningless, because you don't want the memory to be managed.
However, if I made a Card class, it would contain just one or two integers, and it would be immutable (unless these are magic cards that can change their suit and value). Therefore I'd only use copies of cards.
When you return, you might prefer a reference (unless you want to store pointers to the returned values, which would end up looking rather bizarre). But personally, again, I'd just return by value.
I think Ferdinand's answer above is the way to go but I wanted to comment on the use of boost::shared_ptr in this instance.
How heavy are your Card objects? If they're small enough (a few int's say) then it would probably be better just to copy the cards themselves than use boost::shared_ptr since copying boost::shared_ptr's is not cheap (due to thread synchronization on the reference count). This is predicated on your Card objects not needing to have unique identities, e.g. one ten of spades Card object is as good as any other, though.
精彩评论