patterncppMinor
Card class for card games
Viewed 0 times
classforcardgames
Problem
I'm attempting to improve my coding style by concentrating on simple and focused OOP client-server interfaces and error handling - atm in the context of designing a Blackjack game. I'm starting with the Card class that represents an individual card. I'm looking for any improvements on or flaws with my approach.
The client can create a card either using the Card enum constan
#include
#include
#include
class Card
{
public:
enum Ranks { Ace = 1, Ten = 10, Jack, Queen, King };
enum Suits { Spades, Hearts, Diamonds, Clubs };
Card( int rank, int suit ) throw(std::range_error)
{
if( rank 13 ) {
std::stringstream errMsg;
errMsg 3 ) {
std::stringstream errMsg;
errMsg << "Card(): suit '" << suit << "' out of range";
throw std::range_error( errMsg.str() );
}
m_rank = rank;
m_suit = suit;
}
int rank() const { return m_rank; }
int suit() const { return m_suit; }
std::string str( bool useStrings = false ) const
{
std::stringstream ret;
if( useStrings )
ret << sRanks[m_rank] << " of " << sSuits[m_suit];
else
ret << cRanks[m_rank] << cSuits[m_suit];
return ret.str();
}
private:
static const char cRanks[14];
static const char cSuits[4]
static const std::string sRanks[14];
static const std::string sSuits[4];
int m_rank;
int m_suit;
};
const std::string Card::sSuits[4] = {"Spades", "Hearts", "Diamonds", "Clubs"};
const std::string Card::sRanks[14] = { "\0", "Ace", "Two", "Three", "Four", "Five",
"Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King" };
const char Card::cSuits[4] = { 's', 'h', 'd', 'c' };
const char Card::cRanks[14] = { '\0', 'A', '2', '3', '4', '5', '6', '7',
'8', '9', 'T', 'J', 'Q', 'K' };The client can create a card either using the Card enum constan
Solution
If you are going to go to the trouble of creating Rank and Suit enum then use them.
I would have done
That way you can't accidentally go:
The exception specifications have been deprecated. They were mostly useless anyway (if you fail and throw an illegal exception it causes your application to terminate without unwinding the stack). So in general don't use them. For the one case where they are usfull (no throw specifications) they are good but I use them more as documentation to remind me that I should make sure the method does not throw.
But yes the constructor should throw an exception if it gets a value outside the expected range. There is no point in allowing an invalid object to propagate threw your code it will just cause problems with your invariants.
Prefer to use the initializer list rather than initializing variables in the constructor.
Again another place that it would have been nice to use the enum to make the code more eadable:
Try:
Hate getters/setter they spoil encapsulation. Do you really need to get the values of the card? Any operation that manipulates the card is usually a method on the card or a friendly class.
Don't try and write C++ like Java. We don;t need a string function. It is much more preferable (and flexable) to write an output (and probably input) stream operator.
I would have done:
Adding Cards to a deck. (Based on comment below)
Card( int rank, int suit ) throw(std::range_error)I would have done
Card(Rank rank,Suit suit )That way you can't accidentally go:
Card(Card::Hearts,2) The compiler type checking will catch this at compiletime. Just use the enums all through your code. C++ is a strongly typed language use the compiler to catch your errors.The exception specifications have been deprecated. They were mostly useless anyway (if you fail and throw an illegal exception it causes your application to terminate without unwinding the stack). So in general don't use them. For the one case where they are usfull (no throw specifications) they are good but I use them more as documentation to remind me that I should make sure the method does not throw.
But yes the constructor should throw an exception if it gets a value outside the expected range. There is no point in allowing an invalid object to propagate threw your code it will just cause problems with your invariants.
Prefer to use the initializer list rather than initializing variables in the constructor.
Card(Rank rank, Suit suit)
: m_rank(rank)
, m_suit(suit)Again another place that it would have been nice to use the enum to make the code more eadable:
if( rank 13 ) {Try:
if( rank King ) {Hate getters/setter they spoil encapsulation. Do you really need to get the values of the card? Any operation that manipulates the card is usually a method on the card or a friendly class.
int rank() const { return m_rank; }
int suit() const { return m_suit; }Don't try and write C++ like Java. We don;t need a string function. It is much more preferable (and flexable) to write an output (and probably input) stream operator.
std::string str() const
{
return std::string( Ranks[m_rank] + Suits[m_suit] );
}I would have done:
friend std::ostream& operator<<(std::ostream& stream, Card const& card)
{
return stream << card.Ranks[card.m_rank] << card.Suits[card.m_suit];
}Adding Cards to a deck. (Based on comment below)
#include
class Card
{
public:
enum Ranks { Ace = 1, Ten = 10, Jack, Queen, King };
enum Suits { Spades, Hearts, Diamonds, Clubs };
Card(Ranks rank, Suits suit){}
};
// Need to add the ++ operators for Ranks/Suits
Card::Ranks& operator++(Card::Ranks& r)
{
return r = Card::Ranks(static_cast(r)+1);
}
Card::Suits& operator++(Card::Suits& s)
{
return s = Card::Suits(static_cast(s)+1);
}
int main()
{
std::vector deck;
// Does not look that verbose to me.
for(Card::Ranks rank=Card::Ace;rank <= Card::King;++rank)
{
for(Card::Suits suit=Card::Spades;suit <= Card::Clubs;++suit)
{
deck.push_back(Card(rank, suit));
}
}
// Compared to
for(int rank=Card::Ace;rank <= Card::King;++rank)
{
for(int suit=Card::Spades;suit <= Card::Clubs;++suit)
{
deck.push_back(Card(rank, suit));
}
}
}Code Snippets
Card( int rank, int suit ) throw(std::range_error)Card(Rank rank,Suit suit )Card(Rank rank, Suit suit)
: m_rank(rank)
, m_suit(suit)if( rank < 1 || rank > 13 ) {if( rank < Ace || rank > King ) {Context
StackExchange Code Review Q#9676, answer score: 6
Revisions (0)
No revisions yet.