Parent – Child Implementation: Raw Pointers

This is a follow-on post from Implementing Parent – Child and Similar Relationships. In this post we will look at implementing a parent – child relationship using raw pointers 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 RawPointer.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 RawPointer folder on GitHub.

// ParentChild.h
#pragma once

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

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

class Parent;

class Child {
public:
    explicit Child(ID id) : Child(nullptr, id) {}
    Child(Parent* parent, 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(Parent* pParent) noexcept { m_parent = pParent; }
    Parent* m_parent;
    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 = " << pChild->m_parent << ", id = " << pChild->m_id << '\n';
    }
    else {
        os << "nullptr\n";
    }
    return os;
}

class Parent {
public:
    Parent() {}
    ~Parent() noexcept {
        while (m_children.size() > 0) {
            auto pChild = removeChild(m_children[0]->getID());
            delete pChild;
        }
    }
    void addChild(Child* child) {
        child->setParent(this);
        m_children.push_back(child);
    }
    Child* removeChild(ID id) {
        Child child(nullptr, 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()) {
            Child* rChild = *iter;
            m_children.erase(iter);
            rChild->setParent(nullptr);
            return rChild;
        }
        else {
            return nullptr;
        }
    }
    Child* removeChild(Child* pChild) {
        return removeChild(pChild->getID());
    }
    Child* findChild(ID id) {
        Child child(nullptr, 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 nullptr;
        }
    }
    friend std::ostream& operator<<(std::ostream& os, const Parent& parent);
private:
    std::vector<Child*> m_children;
};

std::ostream& operator<<(std::ostream& os, const Parent& parent) {
    os << "Parent =" << &parent << ":\n";
    for (const auto& pChild : parent.m_children) {
       os << "Child: " << pChild;
    }
    return os;
}
// RawPointer.cpp
#include "ParentChild.h"

Child::Child(Parent* parent, ID id) : m_parent(parent), m_id(id) {
    if (parent != nullptr) {
        parent->addChild(this);
    }
}

Child::~Child() noexcept {
    if (m_parent != nullptr) {
        m_parent->removeChild(this);
    }
}

Child* addChild3(Parent* parent)
{
    Child* pChild = new Child(parent, e3);
    return pChild;
}

int main()
{
    Parent* pParent = new Parent();
    Child* pChild1 = new Child(pParent, e1);
    std::cout << "child1: " << pChild1;
    Child* pChild2 = new Child(e2);
    std::cout << "child2 before addChild call: " << pChild2;
    pParent->addChild(pChild2);
    std::cout << "child2 after addChild call: " << pChild2;
    Child* pChild3 = addChild3(pParent);
    std::cout << "child3: " << pChild3;
    std::cout << "Parent after three children added:\n" << *pParent << '\n';
    Child* pChild4 = pParent->findChild(e2); // return pointer to child2
    std::cout << "Parent after findChild call:\n" << *pParent << '\n';
    std::cout << "child4: " << pChild4;
    Child* pChild5 = pParent->removeChild(e1);
    std::cout << "child5: " << pChild5;
    std::cout << "Parent after removeChild(e1) call:\n" << *pParent << '\n';
    Child* pChild6 = pParent->removeChild(pChild4);
    std::cout << "child6: " << pChild6;
    std::cout << "Parent after removeChild(pChild4) call:\n" << *pParent << '\n';
    Child* pChild7 = pParent->removeChild(e1);
    std::cout << "pChild7: " << pChild7;
    Child* pChild8 = pParent->removeChild(pChild4);
    std::cout << "pChild8: " << pChild8;

    delete pParent;
    delete pChild5;
    delete pChild6;

   std::cout << "Press 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:

child1: parent = 0000010DA7B008B0, id = 1
child2 before addChild call: parent = 0000000000000000, id = 2
child2 after addChild call: parent = 0000010DA7B008B0, id = 2
child3: parent = 0000010DA7B008B0, id = 3
Parent after three children added:
Parent =0000010DA7B008B0:
Child: parent = 0000010DA7B008B0, id = 1
Child: parent = 0000010DA7B008B0, id = 2
Child: parent = 0000010DA7B008B0, id = 3

Parent after findChild call:
Parent =0000010DA7B008B0:
Child: parent = 0000010DA7B008B0, id = 1
Child: parent = 0000010DA7B008B0, id = 2
Child: parent = 0000010DA7B008B0, id = 3

child4: parent = 0000010DA7B008B0, id = 2
child5: parent = 0000000000000000, id = 1
Parent after removeChild(e1) call:
Parent =0000010DA7B008B0:
Child: parent = 0000010DA7B008B0, id = 2
Child: parent = 0000010DA7B008B0, id = 3

child6: parent = 0000000000000000, id = 2
Parent after removeChild(pChild4) call:
Parent =0000010DA7B008B0:
Child: parent = 0000010DA7B008B0, id = 3

pChild7: nullptr
pChild8: nullptr

A few 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 raw pointer 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 destructor must destroy all of the Child objects that the Parent object owns.
  5. Because there may be multiple pointers to the same Child object, it is unclear who has responsibility for deleting the object. This leads to memory leaks, and even worse, attempts to delete the same object multiple times. Ownership of a Child object can be indicated using owner<T> from the the Guideline Support Library but owner<T> documents and does not enforce ownership transfers. See the C++ Core Guidelines for information on owner<T>.

Pros

  1. With appropriate modifications to backport the code above, it may be used with C++98.
  2. Developers who have not kept up with recent C++ language changes are familiar with this programming idiom.

Cons

  1. Ownership of Child objects is not explicitly known. It is the coder’s responsibility to ensure that every Child object is deleted once and only once.
Advertisements

4 thoughts on “Parent – Child Implementation: Raw Pointers

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

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

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

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