Parent – Child Implementation: unique_ptr

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

for the first three posts.

In this post we will look at implementing a parent – child relationship using unique_ptr. unique_ptr is insufficient by itself to implement parent – child relationships; shared_ptrs and raw pointers are also used in the example below. The pros and cons of this approach are also discussed.

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 UniquePointer.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 UniquePointer folder on GitHub.

// ParentChild.h
#pragma once

#include <vector>
#include <algorithm>
#include <iostream>
#include <memory>
#include <assert.h>

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

class Parent;

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

    std::shared_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 Child* pChild) {
    if (pChild) {
        os << "parent = ";
        if (pChild->m_pParent) {
            os << pChild->m_pParent << ", id = " << pChild->m_id << '\n';
        }
        else {
            os << "nullptr, id = " << pChild->m_id << '\n';
        }
    }
    else {
        os << "nullptr\n";
    }
    return os;
}


class Parent {
public:
    Parent() {}
    ~Parent() noexcept {}
    static void addChild(std::shared_ptr<Parent>& pParent, std::unique_ptr<Child>& pChild) {
        pChild->setParent(pParent);
        pParent->m_children.push_back(std::forward<std::unique_ptr<Child>>(pChild));
    }
    std::unique_ptr<Child> removeChild(ID id);
    std::unique_ptr<Child> removeChild(Child* pChild) {
        return removeChild(pChild->getID());
    }
    Child* findChild(ID id);
    friend std::ostream& operator<<(std::ostream& os, const std::shared_ptr<Parent>& pParent);
private:
    std::vector<std::unique_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::unique_ptr<Child>& pChild : pParent->m_children) {
            os << "Child: " << pChild.get();
        }
    }
    else {
        os << "Parent = nullptr\n";
    }
    return os;
}
// UniquePointer.cpp
#include "ParentChild.h"

std::unique_ptr Parent::removeChild(ID id) {
    std::unique_ptr pChild = std::make_unique(id);
    auto iter = std::find_if(m_children.begin(), m_children.end(),
        [&pChild](const auto& ch) ->bool { return *pChild == *ch; });
    if (iter != m_children.end()) {
        m_children.erase(iter);
        return pChild;
    }
    else {
        return std::unique_ptr(nullptr);
    }
}

Child* Parent::findChild(ID id) {
    std::unique_ptr pChild = std::make_unique(id);
    auto iter = std::find_if(m_children.begin(), m_children.end(),
        [&pChild](const auto& ch) ->bool { return *pChild == *ch; });
    if (iter != m_children.end()) {
        return iter->get();
    }
    else {
        return nullptr;
    }
}

int main()
{
    std::shared_ptr pParent = std::make_shared();
    std::unique_ptr pChild1 = std::make_unique(e1);
    std::cout << "pChild1 after create: " << pChild1.get();
    std::cout << "pParent after pChild1 created: " << pParent;
    Parent::addChild(pParent, pChild1);
    std::cout << "pParent after pChild1 added: " << pParent;
    std::cout << "pChild1 after addChild: " << pChild1.get();
    std::unique_ptr pChild2 = std::make_unique(e2);
    std::unique_ptr pChild3 = std::make_unique(e3);
    Parent::addChild(pParent, pChild3);
    std::cout << "pParent after pChild3 added: " << pParent;  Child* pChild4 = pParent->findChild(e1);
    std::cout << "pChild4: " << pChild4;
    std::cout << "pParent after findChild call: " << pParent;  auto pChild5 = pParent->removeChild(e3);
    std::cout << "pChild5: " << pChild5.get();
    std::cout << "pParent after removeChild(e3): " << pParent;  std::unique_ptr pChild6 = pParent->removeChild(pChild4);
    std::cout << "pChild6: " << pChild6.get();
    std::cout << "pParent after removeChild(pChild6): " << pParent;  auto pChild7 = pParent->removeChild(e1);
    std::cout << "pChild7: " << pChild7.get();  auto pChild8 = pParent->removeChild(pChild4);
    std::cout << "pChild8: " << pChild8.get();

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

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 unique_ptr to each of the children that it owns, and each Child object holding a shared_ptr to its Parent object. If the Parent objects held a shared_ptr to their Child 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 shared_ptr to the Parent object, and a unique_ptr to the Child object to be added. The method must be static because a shared_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 normally be used.
  7. Parent::findChild returns a raw pointer to the Child object. It cannot return a shared_ptr, weak_ptr, or unique_ptr because the Parent object uses a unique_ptr to reference the Child object. A unique_ptr cannot be converted to a shared_ptr. If a unique_ptr to the Child object is returned, then the unique_ptr stored by the Parent object would be invalidated and ownership would be transferred to the calling object.
  8. Parent::removeChild returns a unique_ptr to the Child object being removed because ownership must be transferred from the Parent object to the object that called Parent::removeChild.

Pros

  1. Ownership of the Child objects is via a unique_ptr, and ownership of Parent objects is via a shared_ptr, 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 use of unique_ptr ensures that each Child object is automatically deleted when the vector holding the Child objects is destroyed.

Cons

  1. unique_ptrs must be moved rather than copied, so both a move constructor and a move operator= method must be implemented for the Child class. This is extra code to maintain which might not otherwise be needed.
  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. This implementation uses a combination of raw pointers, unique_ptrs, and shared_ptrs. Therefore, it suffers from most or all of the problems that the implementation for each pointer type suffers from. See the posts for the other implementations to view those problems.
Advertisements

One thought on “Parent – Child Implementation: unique_ptr

  1. 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