可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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 Card
s. 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 Card
s.
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 Card
s in _shoe
in your _deck
without any memory management issues, but note that you must not add or remove any of the Card
s in the _shoe
during the lifetime of the game otherwise you will invalidate all of the pointers in _deck
.
回答2:
I think there're two mistakes:
- 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?
- 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.
回答3:
Initial thoughts:
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;
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.
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.
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.
回答4:
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.
回答5:
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;
}
回答6:
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.