Multi-Level Parent – Child Implementation: shared_ptr

The last few posts have discussed how to implement parent – child relationships using raw pointers, shared_ptrs, and unique_ptrs. In those relationships there are two object types: parent and child. This post will extend these relationships to allow any child to possibly be the parent of additional children. Such a relationship exists frequently with widgets in graphical user interface libraries. For example, a top-level window (main window or dialog) might contain a group box that in turn contains a number of checkboxes, buttons, and another group box that contains additional widgets. A drop-down menu contains menu items, some of which might be menus that contains other menu items, and so forth.

Here is a simple implementation, first the header file, and then the source file containing additional implementation code for the classes and a main function that exercises the classes. This code is available in the BaseDerived folder in my ParentChild GitHub repository.

// BaseDerived.h
#pragma once

#include <memory>
#include <vector>
#include <iostream>
#include <typeinfo>

enum ID {
    e1 = 1,
    e2, 
    e3,
    e4,
    e5
};

class Base {
public:
    explicit Base(ID id) : m_id(id) {}
    virtual ~Base() {}
    static void addChild(std::weak_ptr<Base> parent, std::shared_ptr<Base> child);
    std::shared_ptr<Base> Base::removeChild(ID id);
    std::shared_ptr<Base> removeChild(std::shared_ptr<Base>& pChild)
    {
        return removeChild(pChild->getID());
    }
    std::shared_ptr<Base> findChild(ID id);
    ID getID() { return m_id; }
    friend bool operator==(const Base& lhs, const Base& rhs);
    friend std::ostream& operator<<(std::ostream& os, const Base& base);
private:
    void setParent(std::weak_ptr<Base>& wParent) { m_pParent = wParent; }
    void clearParent();

    std::weak_ptr<Base> m_pParent;
    std::vector < std::shared_ptr<Base>> m_children;
    ID m_id;
};

bool operator==(const Base& lhs, const Base& rhs)
{
    return lhs.m_id == rhs.m_id;
}

std::ostream& operator<<(std::ostream& os, const Base& base)
{
    os << typeid(base).name() << ": id = " << base.m_id << "\n";
    for (auto& pChild : base.m_children) {
        auto wB = pChild->m_pParent;
        while (wB.lock()) {
            os << "\t";
            std::shared_ptr<Base> pB = wB.lock();
            wB = pB->m_pParent;
        }
        os << *pChild;
    }
    return os;
}

class A : public Base
{
public:
    explicit A(ID id) : Base(id) {}
};

class B : public Base 
{
public:
    explicit B(ID id) : Base(id) {}
};

class C : public Base
{
public:
    explicit C(ID id) : Base(id) {}
};

// BaseDerived.cpp

#include <algorithm>
#include "BaseDerived.h"

void Base::addChild(std::weak_ptr<Base> pParent, std::shared_ptr<Base> pChild)
{
    auto sParent = pParent.lock();
    if (sParent && sParent == pChild) {
        return;
    }
    if (auto pChildsParent = pChild->m_pParent.lock()) {
        pChildsParent->removeChild(pChild);
    }
    sParent->m_children.push_back(pChild);
    pChild->setParent(pParent);
}

std::shared_ptr<Base> Base::removeChild(ID id) {
    Base child(id);
    auto iter = std::find_if(m_children.begin(), m_children.end(),
        [&child](const auto& ch) ->bool { return child == *ch; });
    if (iter != m_children.end()) {
        auto rChild = *iter;
        m_children.erase(iter);
        rChild->clearParent();
        return rChild;
    }
    else {
        return std::shared_ptr<Base>();
    }
}

std::shared_ptr<Base> Base::findChild(ID id)
{
    Base child(id);
    auto iter = std::find_if(m_children.begin(), m_children.end(),
        [&child](const auto& ch) ->bool { return child == *ch; });
    if(iter != m_children.end()) {
        return *iter;
    }
    else {
        return std::shared_ptr<Base>();
 }
}

void Base::clearParent()
{
    if (auto pParent = m_pParent.lock()) {
        pParent->removeChild(m_id);
    }
    setParent(std::weak_ptr<Base>());
}

int main()
{
    std::shared_ptr<A> pA1 = std::make_shared<A>(e1);
    std::shared_ptr<B> pB1 = std::make_shared<B>(e2);
    std::shared_ptr<B> pB2 = std::make_shared<B>(e3);
    std::shared_ptr<C> pC1 = std::make_shared<C>(e4);
    std::shared_ptr<C> pC2 = std::make_shared<C>(e5);
    std::weak_ptr<A> wA1 = pA1;
    std::weak_ptr<B> wB1 = pB1;
    A::addChild( wA1, pB1);
    A::addChild(wA1, pB2);
    B::addChild(wB1, pC1);
    B::addChild(wB1, pC2);
    std::cout << *pA1;

    std::shared_ptr<Base> pB3 = pA1->removeChild(e3);
    std::weak_ptr<C> wC2 = pC2;
    C::addChild(wC2, pB3);
    std::cout << '\n' << *pA1;

    std::shared_ptr<Base> pB4 = pA1->findChild(e2);
    std::cout << "\npB4: " << *pB4;
    std::cout << "\npA1 after findChild: " << *pA1;

    std::cout << "\nPress any non-whitespace character key and then the Enter key to terminate:" << std::endl;
    char c;
    std::cin >> c;
}

Here is the output from the program:

class A: id = 1
    class B: id = 2
        class C: id = 4
        class C: id = 5
    class B: id = 3

class A: id = 1
    class B: id = 2
        class C: id = 4
        class C: id = 5
            class B: id = 3

pB4: class B: id = 2
        class C: id = 4
        class C: id = 5
            class B: id = 3

pA1 after findChild: class A: id = 1
    class B: id = 2
        class C: id = 4
        class C: id = 5
            class B: id = 3

Things to Note:

  1. The base class contains a vector of shared_ptrs to children objects.
  2. The base class contains a weak_ptr to a parent object.
  3. Classes A, B, and C are all derived from Base.
  4. Base::addChild is a static method that takes both a weak_ptr to a parent object and a shared_ptr to the child to add to the children held by the parent. After appropriate checks, the shared_ptr to the child is added to the vector of child objects in the parent pointed to by the weak_ptr, and then sets the added child’s parent to the value of the weak_ptr.
  5. There are two Base::removeChild methods, both of which return a shared_ptr<Base> to the child object being removed.
  6. Base::findChild also returns a shared_ptr<Base> to the child object being removed.

Analysis of This Solution

The previous posts looked at the pros and cons of each approach. Since I am providing only one approach here to coding multi-level parent-child relationships, I have decided to simply discuss the solution.

  1. This solution is similar to the parent – child shared_ptr solution.
  2. It is possible to implement this multi-level relationship using raw pointers and unique_ptrs as well, but those implementations suffer from the same potential problems that were listed in the parent – child posts.
  3. As I noted in the parent – child shared_ptr and unique_ptr implementations, a static method (or a function) is needed to add a child to a parent object because smart pointers to both the parent and child objects are required. I find this esthetically unsatisfying.

The final post in this series will review and compare the parent – child and multi-level parent – child relationships.

Advertisements

2 thoughts on “Multi-Level Parent – Child Implementation: shared_ptr

  1. Pingback: Review of Parent – Child and Multi-Level Parent – Child Implementations | Using C++

  2. Pingback: Taking Out The Garbage | Jim's Adventures in Programming

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s