My current two projects has me delving into the finer points of C++. If you read any of my previous issues, you may know some of this already.
I have a "Layer" clsss that is a container of drawn objects that are to represent a specific layer of data taken from a map class. There are several "layers" that can be drawn at one time and the objects within the layer can be different types. The actual objects that they layer manages and uses to draw are inherited from two different classes. For simplicity's sake, lets call them "Shape" and "CellPointer". We will call the template class that is managed by the layer and inherits both of these "TileTemplate"
I am having a problem when I allocate the array. The layer object works with these objects as "shapes" because that is its primary resposibility. So as far as the Layer is concerned, it is an array of "Shapes"
Shape* mObjectArray.
However, when I allocate the object, I allocate it using the "TileTemplate" class since Shape needs to know where it gets its data (hence its need to also inherite the "CellPointer" interface).
The problem is that sizeof(Shape) = 8 and sizeof(TileTemplate) = 16. When I allocate the array I basically use this kind of allocation:
mObjectArray = new TileTemplate[numberOfSlots];
However, when I cycle through and link the objects to their appropriate cells, I find that only half the objects reference legitimate "TileTemplates".
I am presuming that is because that it is only allocating 8 bytes for the shape when in reality it needs to allocate 16 bytes for the TileTemplate.
I was surprised with this behavior since I thought that the new keyword would pay attention to the object being allocated (TileTemplate type) rather than the pointer type (Shape). To me it would make more sense to have to alter the method that accesses the array (so that it incremented by 16 rather than 8).
This brings up several questions.
1. Why is the allocation done this way?
2. In a class that inherits multiple classes (either through multiple inheritance or even as a result of inheriting from a chain of objects), how are the inherited objects in a class allocated in the array? Are they allocated along with (in the same consecutive block as) the child object? or are they all allocated separately into different blocks?
3. How do I ensure enough space is allocated for the object being used with the "new" keyword (16 bytes or some other number depending on the size of the object) rather than the pointer type size (8 bytes)?
EDIT: my first thought is to use a union class of the types involved and using that class as the pointer type... but that may or may not work depending on the answer to #2. There is also the problem of knowing the type and size of all types a priori (ahead of time)
When I allocate the array I basically use this kind of allocation:
When you say basically, what do you mean? How do you do it exactly? Producing simplified code that reproduces the problem would be about 100 more times effective than supplying a written description of what is happening.
1 - It isn't.
2 - An object takes up a contiguous area of memory (ignoring dynamic allocation by the object or its members.)
Ok... maybe I shouldn't have said "basically" This is literally how it works in my code. The only changes are that I simplified the class names to make them more abstract and easy to follow.
1. well... yes it is unless my assumptions about why am getting the results I am are wrong (which you haven't addressed).
2. Thank you.
3. I did. As far as I understand it is ok to assign a base class pointer the result of a derived class' new allocation. However, it allocated 8 bytes for each item in the array instead of 16 like it should have.
Here is the actual code: (mObjectArray above is equivalent to mItemCollection below.)
template<class PlotLinkedItemType>
void initialize(CvMap* map)
{
static_assert ( std::is_base_of<IPlotLinkedItem, PlotLinkedItemType>::value,
"you must use an item derived from IPlotLinkedItem");
if (mItemCollection)
throw"Item Collection Already Initialized";
// mSize for the test map is 24000
mItemCollection = new PlotLinkedItemType[mSize];
int counter = 0;
CvPlot* plot;
// SquareTile is the PlotLinkedItemType for the test instance
int c = SquareTile::getAC(); // 24000 allocations performed
int s1 = sizeof(SquareTile); // 16
int s2 = sizeof(PlotLinkedItemType); //16
int s3 = sizeof(QGraphicsItem); //8
for (int x = 0; x < map->width(); ++x)
{
for (int y = 0; y < map->height(); ++y)
{
if (counter == 12000)
{
bool stop = true; // everything seems valid....
// the first 12000 items cast correctly. Then dynamic cast below doesn't work. Presumably because only 8 bytes were allocated for each item
}
plot = &map->plots()[x][y];
PlotLinkedItemType* intermediateItem = dynamic_cast<PlotLinkedItemType*>(&mItemCollection[counter]);
IPlotLinkedItem* item = (IPlotLinkedItem*) intermediateItem;
// this code works for the first 12000 items.
if (item)
item->setPlot(plot);
++counter;
}
}
}
EDIT: FYI, the Plot PlotLinkedItemType is the "TileTemplate" I mention in the first post. It is always a class that is ultimately derived from QGraphicsItem (the shape type) and IPlotLinkedItem (the pointer interface).
I found the problem myself. Since I was accessing mItemCollection with a counter instead of a pointer that was incremented appropriately, it was miscalculating the object address using 8 as the object size instead of 16. The following code for the initilization loop works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
PlotLinkedItemType* object = dynamic_cast<PlotLinkedItemType*>(mItemCollection);
CvPlot* plot;
mObjectSize = sizeof(PlotLinkedItemType);
for (int x = 0; x < map->width(); ++x)
{
for (int y = 0; y < map->height(); ++y)
{
plot = &map->plots()[x][y];
IPlotLinkedItem* item = (IPlotLinkedItem*) object;
if (item)
item->setPlot(plot);
++object;
}
}