I am struggling to express a list containing various derived class instances in a container object according to a common interface. Adding them to a vector affords options, using the base class, pointers and references. Casting them to concrete types using a discriminator is proving problematic later.
In C#, give or take, I would express it this way:
public interface IOrderable
{
decimal GetUnitPrice( );
}
public class BaseProduct
{
public Guid Id {get;set;}
public string Name {get;set;}
public decimal UnitPrice {get;set;}
//
public GetUnitPrice( return UnitPrice; }
}
public class Digital : BaseProduct, IOrderable
{
public int Bytes {get;set;}
}
public class Physical : BaseProduct, IOrderable
{
public int MassInGrammes {get;set}
}
public class Order
{
public List<IOrderable> listOfItems;
public int TotalBytes()
{
int t = 0;
foreach( var i in listOfItems )
{
var d = i as Digital; // <-- in C# this patterny works fine
if( d != null ) t = t + d.Bytes;
}
return t;
}
public decimal TotalMass() { ... }
}
When I try to achieve this in C++, I begin with the base class, and create a vector.
class BaseProduct
{
public:
string type; // <-- discriminator
etc.
}
class Order
{
public:
vector<BaseProduct> listOfItems;
int totalBytes() {
int t=0;
for( BaseProduct bp: listOfItems )
{
if( bp.type == "digital" )
... and this is where the wheels come off
how to go from BaseProuct to Digital?
if( bp.type == "physical" )
... etc.
}
return t;
}
}
If I create a list based on pointers I can see how to reinterpret_cast to a known derived type. This is my best guess.
I could not get a version with references to objects to work - the vector definition - closest was wrapped_reference<object&>.
I feel am making heavy weather of this, largely because of C++'s focus on object ownership/lifetime, and the general advice that pointers are somehow evil, even in modern C++. (How things have changed! C++ WAS pointer-land once!)
What then is a good (not necessarily best) pattern for this kind of mixed type list in C++?
(Please note, the above is only pseudo code, the problem emerged in a knarly byte-level message parser, where there is one class per message type. Two is enough to find a pattern.)
A
std::vector<BaseObject>contains onlyBaseObjects. It can't hold any other type, no matter if it inherits fromBaseObjector not.Remember, in C++ variables aren't pointers or references unless you declare them to be. That stands in contrast to C#, where all variables of class types are references to dynamically allocated objects.
To have polymorphism in C++ you must use (smart) pointers or references. To fill a container with dynamically allocated objects of types derived from
BaseObject, you'll likely want either astd::vector<std::unique_ptr<BaseObject>>orstd::vector<std::shared_ptr<BaseObject>>.Which one you want depends on your needs:
std::unique_ptrhas less essentially no runtime overhead over a raw pointer, but is limited to unique ownership semantics.std::shared_ptrhas shared ownership semantics more similar to C#'s garbage collected references, but entails some extra runtime overhead.