Apologies for my ignorance. I'm relatively new to C++ from another language. I'm making a program to rank poker hands for an assignment and I'm hitting an error that I don't understand but I'm assuming is a major syntax error. Please be nice, I'm trying to learn. This will not build in Xcode. When I try, I get the following error message:
12 Duplicate Symbols for architecture x86_64
I am really confused by this error. Here is my code:
poker.hpp
#ifndef poker_hpp
#define poker_hpp
#include <stdio.h>
#include <iostream>
#include <array>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
const size_t HAND_ARRAY_SIZE = 5;
const size_t CARD_ARRAY_SIZE = 2; //just in case, might not use it
typedef array<string, HAND_ARRAY_SIZE> hand_t; //typedef for poker hands
typedef array<string, CARD_ARRAY_SIZE> card_t; //just in case, might not use it
const size_t SUIT_ARRAY_SIZE = 4;
static array<string, SUIT_ARRAY_SIZE> suits =
{"C", "D", "H", "S"};
const size_t VALUE_ARRAY_SIZE = 13;
static array<string, VALUE_ARRAY_SIZE> ranks_ace_high =
{"2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A"};
static array<string, VALUE_ARRAY_SIZE> ranks_ace_low =
{"A", "2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K"};
const size_t HAND_RANK_ARRAY_SIZE = 10;
//EDIT: added static
static array<string, HAND_RANK_ARRAY_SIZE> handRanks =
{
"high card",
"pair",
"two pair",
"three of a kind",
"straight"
"flush",
"full house",
"four of a kind",
"straight flush",
"royal flush"
};
class Hand {
private:
hand_t cards;
string type();
float value();
bool acesLow();
public:
Hand(hand_t cards);
};
#endif /* poker_hpp */
test_hands.cpp
#include "poker.hpp"
hand_t test0 {"TC", "9C", "8C", "7C", "6C"};
hand_t test1 {"8H", "7H", "6H", "5H", "4H"};
hand_t test2 {"8H", "7H", "6H", "5H", "4H"};
hand_t test3 {"6S", "5S", "4S", "3S", "2S"};
hand_t test4 {"7D", "6D", "5D", "4D", "3D"};
hand_t test5 {"7S", "6S", "5S", "4S", "3S"};
hand_t test6 {"KS", "KH", "KC", "KD", "3H"};
hand_t test7 {"7H", "7D", "7S", "7C", "QH"};
hand_t test8 {"7H", "7D", "7S", "7C", "QH"};
Hand.cpp
#include "poker.hpp"
Hand::Hand(hand_t cards){
this->cards = cards;
}
main.cpp
#include "poker.hpp"
#include "test_hands.cpp"
int main() {
Hand hand(test0);
};
There are/were two problems. When you include a file the include directive is effectively replaced with the contents of the named file during the preprocessing phase. This makes one big file containing the contents of the source file and all of the included headers and all of the headers included by other headers that is compiled.
This means you have to be careful when you define something in a header file: Everyone including the header will have its own copy of the definition and this will violate the One Definition Rule unless you take extra steps to lock the definition in the translation unit being compiled (static
linking or anonymous namespaces) or tell the compiler that all of the duplicates will be identical (inline
functions).
In the poker.hpp,
array<string, HAND_RANK_ARRAY_SIZE> handRanks = { ... };
was not static
or bound inside an anonymous namespace so every including source file all had a handRanks
object defined and when the linker put everything together it found all of the handRanks
and refused to proceed.
The second problem was main.cpp included test_hands.cpp. This duplicated everything in test_hands.cpp in main.cpp. The IDE sent test_hands.cpp and main.cpp to the compiler and the output of each source contained everything defined in test_hands.cpp leading to more multiple definitions. Do not include a cpp file. Compile and link them instead.
Anything in test_hands.cpp that needs to be exposed to or shared by other files should be declared in a header that is included by other source files. extern
will help here. That said in this case, just build the test data into main.cpp. There is no good reason to separate the test data from the test driver unless it's huge or complicated and you want to compile it as little as possible.
Tactical note:
All class members and base classes are fully initialized before entering the body of the constructor. This means that in
Hand::Hand(hand_t cards){
this->cards = cards;
}
member cards
is default initialized (which in turn default initializes HAND_ARRAY_SIZE
string
s) and then the member cards
is overwritten with the parameter cards
, rendering the initialization wasted effort. A good optimizing compiler may spot this waste and eliminate it, but instead you can take advantage of the Member Initializer List and make life easier for the compiler by stating exactly the behaviour you want.
Hand::Hand(hand_t cards): cards(cards){
}
Member cards
is now copy-initialized with parameter cards
. Note we also get to reuse the name cards
without ambiguity.
This is important when you have a data structure with expensive default initialization that a compiler cannot easily eliminate and it utterly vital when a base class or member variable cannot be default initialized.