I have project written in C++ that I am working on which has a parent-child relationship where each child has only one parent. I had previously decided after looking at this post that I would make the children know nothing of their parents.
A little background on my project before I go into my question: The parents in this situation are objects representing a collection of interlinked computational “blocks”. The parent (The “Model” class) takes in one or more input values, runs them through its blocks, and the blocks return back one or more output values. The child objects are the Blocks and a Block is owned by exactly one Model. The issue is that a certain kind of Block can contain a Model so that I can nest them and re-use models. Although most blocks know nothing of the Model class, the ModelBlock (inheriting Block) knows about Models and is allowed to encapsulate a Model.
The Question: The above seemed to be working pretty well, at least in the console version of my application. However, I need a GUI to be able to create any large Model without losing my mind and so I have started making one. The issue has reared its ugly head again when I realized that the Blocks in general need to know about their Model so that ModelBlocks can inform a Model when it is being used inside of another Model. This is so that I can easily find “orphaned” Models that are not the root model and not used in any other context (meaning they will never be run). I guess I could make it specific to ModelBlocks, but if I’m going to do something like that, why not make it apply to all the blocks?
In C#, its seemingly not a bad practice to use a circular reference to accomplish this (Entity Framework does it up the wazoo…it wreaks havoc with serializers). However, it seems very taboo in C++ to use circular references. So, I am wondering if the above is a legitimate use. Were I to do this, Blocks would be informed of their parent Model’s destruction so that they know if they are orphans. Even if the above isn’t, are the legitimate uses for circular references in C++ since they seem to be relatively common other languages (of course, that’s assuming that those programs in those other languages using circular references don’t have serious design issues)?
If I didn’t explain my question well enough just let me know and I’ll try to clarify.
EDIT: I should mention that the actual implementations are separate from all this since I am using interfaces (classes with all pure virtual functions) to define how a block looks or how a model looks.
Here is a partial class diagram with my proposed changes:
Here is the source: https://github.com/kcuzner/Simulate/tree/develop.
If you feel like building it, it depends on Qt >= 4.7, boost >= 1.52. The projects are Qt-Creator projects. Console and GUI depend on Engine.
There’s nothing wrong with using a non-owning pointer to someone who points back to you. Just make sure it’s not owning.
3
You can use void *
(or IUnknown *
) to achieve loose coupling and reduce code complexity. It eliminates circular referencing in C++ header files.
// Model.h
class CModel
{
public:
void addBlock(char *str, void** ppBlock);
};
// Block.h
class CBlock
{
public:
void getParent(void** ppModel);
};
Then with appropriate casting, you can get what you need, for example:
#include "Model.h"
#include "Block.h"
// ...
CModel model;
CBlock *pBlock = NULL;
model.addBlock("something", (void**) &pBlock);
CModel *pParent = NULL;
pBlock->getParent((void**) &pParent);
If you add a reference counting system and smart pointers, then, you will have an ecosystem similar to ATL/COM.
You might want to use shared_ptr
for owning links and weak_ptr
for non owning links (child to parent). That way you can destroy the parent, and the child can check if parent exists.
7
You know there are problems where the only good answer is to use a system with better garbage collection than any portable C++ library supplies.
I could name some, but they’re pretty involved. Examples involve things like prolog embedded in C++ and expression trees used in a compiler.
I’ve got a portable mark and sweep garbage collection library working and 90% finished at the moment. Since C++ has limitations it’s not as unrestricted as C#’s – the basic restriction being that objects belong to a thread, each thread has its own garbage collector, and references ACROSS threads have to fall back on atomically handled reference counts like shared_ptr and boost’s intrusive_ptr so cycles that involve ownership by more than one thread won’t be collected. Also in the current version, weak pointers are only available in the owning thread.
After lots of thought, these limits seem like the best compromise you can get with a portable C++ library.
1