My first question is: Is it bad practice to make the array of Card objects public in the Deck class?
Yes. In general, data members should always be private. It is good OOP to create an interface with no associated data that defines what operations can be performed on the object, and then to provide a concrete class that implements that interface. The data is an implementation detail that should not be visible in the interface or even in the fully concrete class. As an example of why it is bad, you might implement your class using an array of Card objects right now, but maybe later you decide to use a bitset where a single bit indicates whether the card is or isn't present in the deck. If you make your Card array object public, changing the representation in that manner would break other users of your class; however, if you keep it private, you can make that change without impacting the users of your class.
Another question: How are objects, such as the Card object used in my blackjack program, generally moved from within an object -like the dealer- to a second object like a player?
It depends on whether the other object needs to access the original card object, whether the other object will hold onto the original object for a long time or only a short time, or if the other object is able to handle only a copy of the card. It also depends on whether the card is a concrete class or a polymorphic type, since polymorphic objects can only be passed by pointer or reference (because passing polymorphic objects by value will lead to code slicing). With concrete objects, you have the option to pass a copy unless you need to modify or access the original object, in which case a reference is needed. Choosing the right way to pass objects is somewhat complicated, but hopefully this will clarify:
Pass by value if:
It is a primitive type or small, non-polymorphic concrete type that does not need to be modified.
Pass by constant reference -- that is const T&
for some type T
-- if:
- You do not need to modify the original object.
- You do not need to read the original object outside of the scope of the function.
- You do not need to read the object beyond the scope of the function, or the type
is non-polymorphic and cheap to copy, so you can create a copy if you need to hang onto
it.
Pass by reference -- that is T&
for some type T
-- if:
- You need to modify the original object.
- You do not need to read/write the original object outside of the scope of the function.
- You do not need to read the object beyond the scope of the function, or the type
is non-polymorphic and cheap to copy, so you can create a copy if you need to hang onto
it.
Pass by constant smart pointer to a const -- that is const shared_ptr<const T>&
for some type T
-- if:
- You need to read the original object both in the scope of the function and beyond.
- You need to read the object both in the scope of the function and beyond, and the type is non-polymorphic so that it is not possible to safely create a copy of it.
Pass by constant smart pointer -- that is const shared_ptr<T>&
for some type T
-- if:
- You need to read and write the orginal object both in the scope of the function and beyond.
I have given each of the above in deliberate order; you should try the first one that will suffice for the job, only moving onto the next if the previous is not sufficient. Also, I should add that boost::call_traits<T>::param_type can help you choose between passing by value and passing by constant reference in the case of concrete non-polymorphic types (it can determine, based on the size of the object, whether pass by value or pass by constant reference is better).