Parent – Child Implementation: shared_ptr

This is the third in a series of posts on parent – child relationships. See

for the first two posts.

In this post we will look at implementing a parent – child relationship using shared_ptr and weak_ptr, and discuss the pros and cons of this approach.

Here is some sample code. There are two files: ParentChild.h, which declares the Parent and Child classes as well as providing some of their implementation, and SharedPointer.cpp, which provides the rest of the Parent and Child class implementations, and a main function that exercises the classes. This code is provided, along with a Visual Studio project in the SharedPointer folder on GitHub.

// ParentChild.h
#pragma once

#include <vector>
#include <algorithm>
#include <iostream>
#include <memory>

enum ID {
    eNoID = 0,
    e1,
    e2,
    e3
};

class Parent;

class Child {
public:
    explicit Child(ID id) : m_pParent(std::weak_ptr<Parent>()), m_id(id) {}
    ~Child() noexcept {}
    const ID getID() const noexcept { return m_id; }
    void clearParent();
    friend std::ostream& operator<<(std::ostream& os, const std::shared_ptr<Child>& pChild);
    friend Parent;
private:
    void setParent(std::weak_ptr<Parent>& pParent) noexcept { m_pParent = pParent; }
    std::weak_ptr<Parent> m_pParent;
    ID m_id;
};

bool operator==(const Child& lhs, const Child& rhs) noexcept {
    return lhs.getID() == rhs.getID();
}

std::ostream& operator<<(std::ostream& os, const std::shared_ptr<Child>& pChild) {
    if (pChild) {
        os << "parent = ";
        if (auto pParent = pChild->m_pParent.lock()) {
            os << pParent << ", id = " << pChild->m_id;
            os << ", use_count = " << pChild.use_count() << '\n';
        }
        else {
            os << "nullptr, id = " << pChild->m_id;
            os << ", use_count = " << pChild.use_count() << '\n';
        }
    }
    else {
        os << "nullptr\n";
    }
    return os;
}

class Parent {
public:
    Parent() {}
    ~Parent() noexcept {}
    static void addChild(std::weak_ptr<Parent>& wParent, std::shared_ptr<Child>& pChild) {
        if (auto pParent = wParent.lock()) {
            pChild->setParent(wParent);
            pParent->m_children.push_back(pChild);
        }
    }
    std::shared_ptr<Child> removeChild(ID id);
    std::shared_ptr<Child> removeChild(std::shared_ptr<Child>& pChild) {
        return removeChild(pChild->getID());
    }
    auto findChild(ID id);

    friend std::ostream& operator<<(std::ostream& os, const std::shared_ptr<Parent>& pParent);
private:
    std::vector<std::shared_ptr<Child>> m_children;
};

std::ostream& operator<<(std::ostream& os, const std::shared_ptr<Parent>& pParent) {
    if (pParent) {
        os << "Parent =" << pParent.get() << ":\n";
        for (const std::shared_ptr<Child>& pChild : pParent->m_children) {
            os << "Child: " << pChild;
        }
    }
    else {
        os << "Parent = nullptr\n";
    }
    return os;
}
// SharedPointer.cpp

#include "ParentChild.h"

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

std::shared_ptr<Child> Parent::removeChild(ID id) {
    Child 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<Child>(nullptr);
    }
}

auto Parent::findChild(ID id) {
    Child 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<Child>(nullptr);
    }
}

int main()
{
    std::shared_ptr<Parent> pParent = std::make_shared<Parent>();
    std::weak_ptr<Parent> wParent = pParent;
    std::shared_ptr<Child> pChild1 = std::make_shared<Child>(e1);
    Parent::addChild(wParent, pChild1);
    std::cout << "pChild1: " << pChild1;
    std::cout << "pParent after creating pChild1: " << pParent;
    std::shared_ptr<Child> pChild2 = std::make_shared<Child>(e2);
    std::cout << "pChild2 before addChild call: " << pChild2;
    Parent::addChild(wParent, pChild2);
    std::cout << "pParent after addChild call: " << pParent;
    std::shared_ptr<Child> pChild3 = std::make_shared<Child>(e3);
    Parent::addChild(wParent, pChild3);
    std::cout << "pParent after three children added:\n" << pParent;
    std::shared_ptr<Child> pChild4 = pParent->findChild(e2);
    std::cout << "pChild4: " << pChild4;
    std::cout << "pParent after findChild call: " << pParent;
    std::shared_ptr<Child> pChild5 = pParent->removeChild(e1);
    std::cout << "pChild5: " << pChild5;
    std::cout << "pParent after removeChild(e1): " << pParent;
    std::shared_ptr<Child> pChild6 = pParent->removeChild(pChild4);
    std::cout << "pChild6: " << pChild6;
    std::cout << "pParent after removeChild(pChild4): " << pParent;
    std::shared_ptr<Child> pChild7 = pParent->removeChild(e1);
    std::cout << "pChild7: " << pChild7;
    std::shared_ptr<Child> pChild8 = pParent->removeChild(pChild4);
    std::cout << "pChild8: " << pChild8;

    pParent.reset();
    std::cout << "pParent after reset: " << pParent;

    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 running the program:

pChild1: parent = 000001C462155650, id = 1, use_count = 2
pParent after creating pChild1: Parent =000001C462155650:
Child: parent = 000001C462155650, id = 1, use_count = 2
pChild2 before addChild call: parent = nullptr, id = 2, use_count = 1
pParent after addChild call: Parent =000001C462155650:
Child: parent = 000001C462155650, id = 1, use_count = 2
Child: parent = 000001C462155650, id = 2, use_count = 2
pParent after three children added:
Parent =000001C462155650:
Child: parent = 000001C462155650, id = 1, use_count = 2
Child: parent = 000001C462155650, id = 2, use_count = 2
Child: parent = 000001C462155650, id = 3, use_count = 2
pChild4: parent = 000001C462155650, id = 2, use_count = 3
pParent after findChild call: Parent =000001C462155650:
Child: parent = 000001C462155650, id = 1, use_count = 2
Child: parent = 000001C462155650, id = 2, use_count = 3
Child: parent = 000001C462155650, id = 3, use_count = 2
pChild5: parent = nullptr, id = 1, use_count = 2
pParent after removeChild(e1): Parent =000001C462155650:
Child: parent = 000001C462155650, id = 2, use_count = 3
Child: parent = 000001C462155650, id = 3, use_count = 2
pChild6: parent = nullptr, id = 2, use_count = 3
pParent after removeChild(pChild4): Parent =000001C462155650:
Child: parent = 000001C462155650, id = 3, use_count = 2
pChild7: nullptr
pChild8: nullptr
pParent after reset: Parent = nullptr

A number of things should be noted:

  1. The Child class contains an ID property which is used to distinguish between the various Child objects.
  2. The Parent class provides two methods for removing children: the first method uses the ID parameter to locate the Child object to remove, and the second method uses a shared_ptr to the Child object to remove.
  3. Parent::findChild finds the Child object with the specified ID. It does not remove the Child object from the list of Child objects in the Parent object.
  4. The Parent and Child classes form a circular reference to each other, with each Parent object holding a shared_ptr to each of the children that it owns, and each Child object holding a weak_ptr to its Parent object. If the Child objects held a shared_ptr to their Parent object, then the Parent and Child objects could never be destroyed; the reference counts would never reach 0.
  5. Parent::addChild is a static method that takes a weak_ptr to the Parent object, and a shared_ptr to the Child object to be added. The method must be static because a weak_ptr is required to the Parent object, and the program cannot generate one directly from the Parent object itself.
  6. Because Parent::addChild is a static method, it could alternatively be Child::addToParent with the same arguments. Lastly, it could be a function, but since it is related to Parent and Child, either of the static methods would typically be used.
  7. Unlike the raw pointer implementation, there is no need to keep track of who owns the Child objects because they are created as the targets of shared_ptrs and are therefore deleted when the reference count of each shared_ptr goes to 0.

Pros

  1. Ownership of the Child objects is shared, so there is no need for the coder to keep track of when the objects must be deleted.
  2. No code is needed in the Parent destructor to destroy the Child objects because the reference counts for the Child objects are automatically decremented when the vector holding the Child objects is destroyed.

Cons

  1. There is greater overhead copying shared_ptrs than copying raw pointers. In most programs, this overhead would be a small very portion of the total program execution time.
  2. A function or static method is required for adding a Child object to a Parent object. Beyond object initialization using builder or factory methods, I find static methods to be esthetically unsatisfying.
  3. There are a number of subtle potential bugs when using shared_ptr. See Dangers of std::shared_ptr for details.
Advertisements

2 thoughts on “Parent – Child Implementation: shared_ptr

  1. Pingback: Parent – Child Implementation: unique_ptr | Using C++

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

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