The following working C++11 code demonstrates working code for a pattern for representing one-to-many relationships between objects.
(A variant of that code is also copied inline at the bottom of this post).
I think these are the desirable features:
- Children only created through owning parent so that they are managed by parent.
std::shared_ptr
and related utilities used for memory management.const
-correctness
Notes:
- The link to GitHub uses abstract names (A, B) instead of the concrete example used here (Model, Component).
- The abstract names in the GitHub version hint at a possible template implementation
Model.h
#ifndef Model_h
#define Model_h
#include "Component.h"
#include <vector>
#include <string>
//! For collection of const children (i.e. each element immutable).
//! Overall collection is also not mutable
typedef std::vector<std::shared_ptr<const Component>> const componentCollectionConstT;
//! Overall collection is not mutable, but each element is mutable
typedef std::vector<std::shared_ptr<Component>> const componentCollectionT;
//! The class for a parent object
class Model :
public std::enable_shared_from_this<Model> // Needed so that a reference to the Model parent object creating the Component child object can be stored by the child component object.
{
//! Children
std::vector<std::shared_ptr<Component>> components;
//! Model name
std::wstring mName;
public:
//! Construct passing in value for name.
//! \param name will be stored as the model name.
Model(const std::wstring name);
//! Getter for name
//! \return the name
const std::wstring getName() const;
/** Get read-only collection of children components
*
* Each child element is immutable.
* \return The collection of children, read-only.
*/
componentCollectionConstT getComponentsReadOnly() const;
/** Get collection of mutable children
*
* Each child element is mutable.
* Overall membership of collection is still fixed.
* \return The collection of mutable children.
*/
componentCollectionT getComponents();
/**
* Create a child component object, of which this object is the parent
* \return a pointer the newly created child component object.
*/
const std::shared_ptr<const Component> createComponent(int);
};
#endif
Model.cpp
#include "Model.h"
#include <memory>
#include <algorithm>
using namespace std;
Model::Model(wstring name) :
mName(name)
{}
const wstring Model::getName() const {
return mName;
}
componentCollectionConstT Model::getComponentsReadOnly() const {
componentCollectionConstT componentsC(components.begin(), components.end()); // Have to copy to get const version.
return componentsC;
}
componentsCollectionT Model::getComponentss() {
return components;
}
const shared_ptr<const Component> Model::createComponent(int count) {
shared_ptr<Component> componentSP(new Component(count, shared_from_this())); // Have to use "shared_from_this"
components.push_back(componentSP);
return componentSP;
}
Component.h
#ifndef Component_h
#define Component_h
#include <memory>
class Model;
//! Components group aspects of the model.
class Component {
friend class Model;
//! Constructor kept private so that only Model (which is a friend) can construct. \see Model::createComponent
Component(const int count, std::weak_ptr<Model> model);
//! Some arbitrary data member as a place-holder for more interesting content in the real world.
int mCount;
//! Reference from child back to parent.
//! This needs to be a weak_ptr to avoid circular ref counting issue.
std::weak_ptr<Model> mModel;
public:
//! Getter for arbitrary data member.
const int getCount() const;
//! Setter for arbitrary data member.
void setCount(const int);
//! Get a weak reference to parent model object.
const std::weak_ptr<const Model> getModel() const;
};
#endif
Component.cpp
#include "Component.h"
#include "Model.h"
using namespace std;
Component::Component(const int count, weak_ptr<Model> model):
mCount(count),
mModel(model)
{}
const int Component::getCount() const {
return mCount;
}
void Component::setCount(const int count) {
mCount = count;
}
const weak_ptr<const Model> Component::getModel() const {
return mModel;
}
main.cpp
/**
* Demonstrates one-to-many relationship with parent ownership semantics,
* and bi-directional navigability.
* Memory management using std::shared_ptr and related utilities.
*
* Components are created through Model, so that they are never orphans.
* Also, the collection of children of a Model object cannot have members added or removed.
*
* So far this has only been tested on XCode 5.1.1 and 6.0.1
*/
#include <iostream>
#include <memory>
#include "Model.h"
#include "Component.h"
using namespace std;
int main(int argc, const char * argv[])
{
// Demonstrate creating a parent model, and creating child Components through the parent.
// The parent is mutable.
// Recommendation: all handle's are shared_ptr. (Or perhaps mandatory, since createComponent calls "shared_from_this"?)
shared_ptr<Model> a(new Model(L"The first model object"));
a->createComponent(5);
a->createComponent(55);
a->createComponent(5555);
a->createComponent(-345);
a->createComponent(2316724);
a->createComponent(0);
componentCollectionT bs = a->getComponents();
//components.push_back(make_shared<Component>(42)); // Compile error <= const (otherwise new children could be "smuggled in"); and Component constructor private.
for(auto b : bs) {
b->setCount(b->getCount()*10000); // Demonstrate that each child is mutable.
auto aName = b->getModel().lock()->getName();
wcout << b->getCount() << " - " << aName << endl;
}
cout << "**********************" << endl;
// Repeat the above output (this demonstrates that we really modified the underlying children, not just copies of them.
for(auto b : a->getComponents()) {
wcout << b->getCount() << " - " << b->getModel().lock()->getName() << endl;
}
cout << "**********************" << endl;
// If we start out with a const parent (i.e. instance of Model), we can't even add children!
const shared_ptr<const Model> ac = const_pointer_cast<const Model>(make_shared<Model>(L"The second model object"));
// ac->createComponent(5); // Compile error <= const.
// componentCollectionT bsc = ac->getComponents(); // Compile error <= const.
componentCollectionConstT bsc = ac->getComponentsReadOnly();
// bsc[1]->setCount(444); // Compile error <= const.
// bsc.push_back(1); // Compile error <= const.
for(auto b : bsc) {
wcout << b->getCount() << " - " << b->getModel().lock()->getName() << endl;
}
cout << "**********************" << endl;
// If we have a const child, then the handle we get to its parent is also const.
const shared_ptr<Component const> b1(a->createComponent(21112));
auto ac2 = b1->getModel().lock();
auto bsc2 = ac2->getComponentsReadOnly();
for(auto b : bsc2) {
wcout << b->getCount() << " - " << b->getModel().lock()->getName() << endl;
}
// ac2->createComponent(5); // Compile error <= const.
// componentCollectionT bsc22 = ac2->getComponents(); // Compile error <= const.
// bsc2[1]->setCount(444); // Compile error <= const.
// bsc2.push_back(1); // Compile error <= const.
return 0;
}