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.

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.

Implementing Parent-Child and Similar Relationships

I have been thinking a lot lately about relationships between objects. These relationships come in three varieties:

  1. One object acts as the “creator” and “destroyer” for other objects. In this case,  the “creator/destroyer” object does not need to keep track of the objects that it created, but the created objects must keep a reference to the “creator/destroyer” so that the objects can destroy themselves. The program must destroy the created objects before destroying the “creator/destroyer” object. One object, the parent, owns the other objects, the children, and must destroy its children objects before destroying itself.One example of this would be in an object-oriented implementation of the Vulkan API. Once a logical device object has been created, it can be used to create and destroy any number of other objects such as swap chains, render passes, pipelines, frame buffers, command pools, semaphores, and so forth. The logical device should not be destroyed until all of the created objects are destroyed. Otherwise, GPU memory leaks occur.
  2. In a parent-child relationship, the children may be created before or after the parent. Child objects may be added or removed from the parent as required. The parent must maintain a reference to each of the children it owns, and each child must maintain a reference to its parent. The program must destroy each non-owned object. One place that a parent-child relationship could be implemented is in Tasks and Task Lists. The Task List would be the parent, holding a number of Task children. Again, the Task List must not be destroyed until all of the Tasks that it “owns” are destroyed.
  3. An extension of the parent-child relationship is where each child could in turn be the parent of other child objects.This sort of relationship shows up in graphical user interface libraries. For example, a main window contains a number of child widgets, each of which may contain zero or more other widgets, which in turn may contain…

The creator- destroyer pattern can be thought of as a specialized case of the parent – child relationship with the parent (the creator – destroyer object) not keeping track of the children objects. This pattern will not be discussed further.

The next several posts will look at three ways of implementing parent – child relationships, and a single way of implementing the multiple level parent-child relationship.

Generalized Parent – Child Relationship

To begin, let’s look at the parent – child relationship:

class Parent {
public:
    Parent(/* args as needed */);
    ~Parent();
    void addChild(RefToChild1 child);
    RefToChild2 findChild(RefToChild1 child);
    RefToChild2 removeChild(RefToChild1 child);
...
private:
    vector m_children;
};

class Child {
public:
    Child(RefToParent parent, /* other args as needed */);
    RefToParent getParent();
    const ID getID();
...
private:
    RefToParent m_parent;
    ID m_id;
};

The next thtee posts will look at Parent and Child classes where RefToChild1, RefToChild2, and RefToParent are raw pointers, shared_ptrs and weak_ptrs, and finally, a combination of raw pointers, shared_ptrs and unique_ptrs. The pros and cons of each approach will be discussed.

Multi-Level Parent – Child Relationships

Following the Parent – Child posts, we will look at the pattern where each class contains a link to a parent object and links to any child objects. Only the shared_ptr implementation will be reviewed; however, a raw pointer and a combination of raw pointer, shared_ptr, and unique_ptr implementations could be developed.

No “code” is shown in this post; that is left for the detailed posts.

DoxyPress and Visual Studio

Last week I wrote about integrating Doxygen with Visual Studio. In one of the comments about that post, legalize suggested that I use its modern C++ replacement DoxyPress instead. DoxyPress is a fork of Doxygen so you should be able to migrate very easily from Doxygen to DoxyPress; there is functionality included to convert an existing Doxygen configuration file to a DoxyPress project file.

Installing DoxyPress

Here are the instructions for using DoxyPress with Visual Studio 2015 and 2017.

  1. Download the 32-bit or 64-bit Windows installer for DoxyPress as appropriate.
  2. Use Run as Administrator to execute the installer. If you do not run the installer as administrator, the installer will not be able to install the files into Program Files (or Program Files (x86) as appropriate). If you execute the installer on a computer running Windows 8/8.1 or 10, you will probably see the following messagebox. If you do not see this messagebox, you may skip forward to step 5.
    SmartScreen
    This is displayed because Microsoft is being cautious and warning you about applications and websites it does not recognize. It does not mean that the installer contains malware, just that it is not in Microsoft’s list of high use applications.
  3. To proceed, click on More info:
    SmartScreen-moreinfo
  4. Either heed the warning and click on the Don’t run button, in which case you cannot install DoxyPress, or click on the Run anyway button to continue.
    Note: I cannot and will not guarantee that the installer for any version of DoxyPress does not contain any malware. As with every application you install, you ultimately take responsibility for that.
  5. The DoxyPress installer dialog will be displayed:
    DoxyPressLanguage
    Choose your preferred language and click Next>.
  6. Continue clicking the Next> button until the installer installs DoxyPress. If the following messagebox is not displayed, proceed to step 7. If the messagebox is displayed, you did not use Run as Administrator.
    DoxyPressNoInstall
    Click the OK button to go back to the installer dialog and click Cancel, then go back to step 2 to rerun the installer using Run as Administrator.
  7. When the installation completes, click on the Finish button to close the installer.

Integrating DoxyPress With Visual Studio

  1. To integrate DoxyPress with Visual Studio, open Visual Studio and select the Tools -> External Tools… menu button. This will open the External Tools dialog:
    ExternalTools
  2. Click on the Add button. The dialog changes to:
    AddExternalTool
  3. Set the Title to DoxyPress, the Command to the executable for DoxyPress (e.g. C:\Program Files\DoxyPress\doxypress.exe), the Arguments to $(ProjectDir)DoxyPress.json, and the Initial directory to $(ProjectDir). Note that there is no ‘\’ character in the Arguments value. Check the Use Output window checkbox. The bottom portion of the dialog box should look similar to this:
    AddDoxyPress
  4. Click the Apply button to add DoxyPress as a menu item.
  5. Again click the Add button.
  6. Set the Title to DoxyPressApp, the Command to the executable for DoxyPressApp (e.g. C:\Program Files\DoxyPress\DoxyPressApp.exe), leave the Arguments blank, and set the Initial directory to $(ProjectDir). Do not check any checkboxes that are not already checked.
  7. Click the OK button to add the DoxyPressApp menu item and close the dialog box.

Generating Documentation the First Time

These instructions assume you have added the appropriate DoxyPress comments to your source code. To use DoxyPress to generate documentation:

  1. In Visual Studio, select the project you are documenting in the Solution Explorer.
  2. If this is the first time you will be generating the documenation for this project, select the Tools -> DoxyPressApp menu item. If this is first time you have ever run DoxyPressApp, the following messagebox will be displayed:DoxyPressAppSetupFileMissingThe setup file contains the location and size of the DoxyPressApp window, a list of the most recently opened DoxyPress project files, and the path to the directory that the last DoxyPress.json file was saved in.
  3. Click on the Default Location button. This saves the settings in C:\Users\<yourusername>\AppData\Local\CS\DoxyPressApp\DoxyPressApp.json. The DoxyPressApp dialog will now open:DoxyPressApp
  4. Select each of the topics in the Setup, Build Settings, and Output Formats tabs to configure the output that will be generated by DoxyPress. The various settings are described in the DoxyPress Project File web page.
  5. When done, select the Run tab:DoxyPressAppRun
  6. Click on the Options for DoxyPress button. This opens the Passed Parameters dialog:NamedParameters
  7. Enter the values you want, then click the Ok button to close the dialog.
  8. Click the Run DoxyPress button. The first time you click on Run DoxyPress, the Save File dialog will open so you can save the DoxyPress project file. Navigate to the project directory (the directory containing the Visual Studio project file (vcxproj file) and enter the file name DoxyPress.json. This file name must match the name specified in the Arguments in step 3 in the section Integrating DoxyPress With Visual Studio, above. Click the Save button to save the file and close the Save File dialog.
  9.  Your documentation will now be generated. If you selected HTML output in the Output Formats tab, click the Display HTML button to view the generated documentation; otherwise, open the generated documentation to ensure that it is created as you would like it.
  10. If you wish to make changes to how the documentation is generated, repeat steps 4 – 9 until you are satisfied. Once you are satisfied with the generated output, close DoxyPressApp.

Regenerating Documentation

When you want to regenerate your documentation, select the project in Visual Studio’s Solution Explorer and then select the Tools -> DoxyPress menu item. It will use the DoxyPress.json file you saved in the instructions above so it is not necessary to run DoxyPressApp again for the selected project.

Doxygen and Visual Studio

Doxygen is a tool for generating documentation from annotated source code. Originally created specifically for C++, it now also supports C, Objective-C, C#, PHP, Java, Python, IDL, Fortran, VHDL, Tcl, and D. Output formats include HTML, Latex, RTF (MS-Word), PostScript, hyperlinked PDF, compressed HTML, and Unix man pages. Although developed in OS X and Linux, there is also an MS Windows executable.

This post will not discuss how to document your source code for use with Doxygen, nor will it list the advantages and disadvantages of using Doxygen. You will have to decide if Doxygen is the right tool for you. You should see the Doxygen website for that. This post will simply show how to use Doxygen with Visual Studio.

There are no extensions for integrating Doxygen with Visual Studio. However, Doxygen, and Doxywizard, a wizard-based executable for creating the configuration file for use with Doxygen, are command line executables which can easily be run from the Visual Studio Tools menu. Adding Doxygen and Doxywizard to the Tools menu is done as follows. The instructions work for both Visual Studio 2015 and Visual Studio 2017.

  1. Download and install the latest Doxygen Windows binary.
  2. Open the Visual Studio Tools dropdown menu and select External Tools…
  3. This opens the External Tools dialog:
    ExternalTools
    Click the Add button. The dialog changes to this:
    AddExternalTool
  4. Change the Title to Doxygen, the command to point to the Doxygen executable (C:\Program Files\doxygen\bin\doxygen.exe on my computer), the arguments to $(ProjectDir)\Doxyfile, and initial directory to $(ProjectDir). Check the Use Output window checkbox. The lower portion of the dialog box will look like this:
    AddDoxygen
    $(ProjectDir) is the macro in Visual Studio that points to the project directory (the directory that contains the project’s vcxproj file).
  5. Click the Apply button to add the Doxygen menu item to the Tools menu.
  6. Click the Add button.
  7. Enter DoxyWizard as the Title, the location of the doxywizard executable  as the Command (e.g. C:\Program Files\doxygen\bin\doxywizard.exe), leave Arguments blank, and $(ProjectDir) as initial directory. Leave all checkboxes unchecked.
  8. Click the OK button to add the DoxyWizard menu item and close the dialog box.

The first time you use Doxygen with a project, select the Tools -> DoxyWizard menu item to open the DoxyWizard dialog, shown here:
DoxyWizardDialog

  1. Step 1: Set the working directory to be the $(ProjectDir) directory. That is the directory containing the project’s vcxproj file.
  2. Step2: Use the Wizard and/or Expert tabs to set the configuration values.
  3. Select the Run tab and then Run doxygen.
  4. If you wish, go back and change the various configuration values.
  5. Once you are satisfied with the values you have set, close the dialog. This will display the Unsaved changes message box. Click Save to save the configuration. Select the directory that contains the project’s vcxproj file. This will ensure that the configuration file is found when you run Doxygen.

You should only run DoxyWizard once for each project that you are documenting. For each subsequent document generation, use Tools -> Doxygen. Provided you saved the configuration file in the correct location, Doxygen will run correctly, saving its generated documentation to the specified directory, and sending its output to the Visual Studio Output window.

 

More on Naked Primitives

The post Strong Typing or Naked Primitives showed an example of problems that can occur when using common variable types (or if you prefer, built-in data types) as argument types in methods and functions, and a potential solution. The post discussed a Size struct with the constructor:

struct Size {
    Size(const uint32_t width, const uint32_t height);
};

and the possible problems that can occur, such as placing the height argument before the width argument.

User Defined Literals continued the example by showing how to use and convert different unit types in the arguments. Specifically, the best way to supply the width and height arguments in units of pixels, inches, and centimetres.

This post will continue to investigate ways of specifying arguments to remove the confusion that can occur when using common variable types as arguments.

Character String to Enumeration

In this post, we will declare a class that partially encapsulates the functionality of C File I/O. A good introduction to C File I/O is provided by programiz.com.

Here is a simple first File class and how to use it. We are interested only in the arguments; the actual implementation is not included. That is up to you to provide.

class File
{
public:
    File(const char* fileName, const char* mode) 
        { /* create file if necessary, and open it */}
    ~File() { /* close file */}
    void print(const char* charString, bool appendReturn) 
        { /* print charsString */}
};

int main()
{
    File file1("file1", "a");
    file1.print("A line", true);
    File file2("file2", "w");
    file2.print("A line, no return", false);
    File file3("file3", "xmf_");
    return 0;
}

The constructor for file1 will create the file if it does not exist, then move the cursor for writing to the file to past the last character in the file. The constructor for file2 will create the file if it does not exist, or erase the contents of the file if it does exist, and place the write cursor at the beginning of the file. The constructor for file3 contains invalid characters in the mode field, so you would have to add code to the File constructor to handle this.

Another problem with the constructor as it is declared is that there are two arguments of type const char* so if you specify the arguments in the wrong order when calling the constructor, the compiler will not catch the error. We will not look at this further as this has already been discussed in Strong Typing or Naked Primitives.

So, our only concern here is the second argument, the mode. Since there are a limited number of values, this appears to be a good candidate for an enumeration instead of a string. Looking at the possible values for mode you will notice that it serves two different purposes:

  1. Indicates that the file should be opened for some combination of reading, writing, or appending.
  2. Indicates whether the file contents are text or binary.

To simplify the code in the constructor, let’s use two separate enumerations, one for the operation type, and one for the contents type. Here is the code resulting from these changes:

enum Mode {
    eRead = 1,
    eWrite = 2,
    eAppend = 4
};

enum Type {
    eText = 1,
    eBinary = 2
};

class File
{
public:
    File(const char* fileName, unsigned int mode, enum Type type) 
        { /* create file if necessary, and open it */}
    ~File() { /* close file */}
    void print(const char* charString, bool appendReturn) 
        { /* print charString */}
};

int main()
{
    File file1("file1", eAppend, eText);
    file1.print("A line", true);
    File file2("file2", eWrite, eText);
    file2.print("A line, no return", false);
    File file3("file3", eRead | eWrite, eText);
    File file4("file4", eBinary | 16, eText);
    return 0;
}

The Mode enumeration contains only three values: eRead, eWrite, and eAppend.  No attempt has been made to distinguish between the fopen modes of “w+” and “r+”. That is left as an exercise for the reader because it is not germane to the topic of this post.

Since a file can be opened for both reading and writing, or reading and appending, the second argument for the File constructor is specified as unsigned int. Multiple values of mode can therefore be OR’ed together. See, for example, the constructor call for file3. Everything looks good so far. Now look at the constructor call for file4. Here the Mode has been set to a combination of eBinary, which is a Type not a Mode, and 16 which has no numerical equivalent in Mode.

One potential solution to this problem is to add two enumeration values to the Mode enumeration: eReadAndWrite, and eReadAndAppend, and to change the second argument type in the File constructor to enum Mode. This works in this case, but what if Mode had a large number of individual values which could be combined in many different ways? Defining a value for every combination would not be a viable option.

The solution is to change the Mode and Type enumerations to both be enum class and to add a class that can OR together multiple Mode values:

enum class Mode : unsigned int {
    eRead = 1,
    eWrite = 2,
    eAppend = 4
};

enum class Type : unsigned int {
    eText = 1,
    eBinary = 2
};

template <typename BitType, typename MaskType = unsigned int>
class Flags
{
public:
 Flags()
 : m_mask(0) {}

 Flags(BitType bit)
 : m_mask(bit) {}

 Flags(Flags<BitType> const& rhs)
 : m_mask(rhs.m_mask) {}

 Flags<BitType> operator|(Flags<BitType> const& rhs) const
 {
 Flags<BitType, MaskType> result(*this);
 result |= rhs;
 return result;
 }

private:
 MaskType m_mask;
};

using ModeFlags = Flags<Mode>;
ModeFlags operator|(Mode bit0, Mode bit1)
{
 return ModeFlags(bit0) | bit1;
}


class File
{
public:
    File(const char* fileName, ModeFlags mode, Type type) 
    { /* create file if necessary, and open it */}
    ~File() { /* close file */}
    void print(const char* charString, bool appendReturn) 
    { /* print charString */}
};

int main()
{
    File file1("file1", Mode::eAppend, Type::eText);
    file1.print("A line", true);
    File file2("file2", Mode::eWrite, Type::eText);
    file2.print("A line, no return", false);
    File file3("file3", Mode::eRead | Mode::eWrite, Type::eText);
    File file4("file4", Type::eBinary | 16, Type::eText);
    return 0;
}

The Flags class has been borrowed from the Vulkan C++ bindings, vulkan.hpp, available as part of the Vulkan SDK, or separately from its GitHub repository. I have included only those parts of the class that are required for this example to compile or not where required. Things to note:

  1. The second argument in the File constructor has been changed to type ModeFlags, which is just an alias for Flags<Mode>.
  2. The line containing the constructor call for file3 now compiles.
  3. The line containing the constructor call for file4 does not compile because Type::eBinary and 16 are both not of type Mode. But, since this is a programming error, that is what we want to happen.

Update (February 17, 2017): A few days after publishing this post, I ran across Alternative to select-many bitmask that discusses a number of methods for combining bitmask bits.

Boolean to Enumeration

In the section above, we changed a character string that could contain a limited number of values to enumerations. By doing so, we ensured that invalid values could not be coded, and we ensured that 6 months from now when you or someone else looks at the code, they will not have to reference the documentation or the class’s declaration to determine the meaning of each argument.

Now we move on to boolean arguments. In the main function in the examples above, there are calls to File::print. The second argument in these calls contains either true or false. Can you tell what those argument values mean without looking at the class declaration? What happens if you change the value of this argument to 6? Hint: the program will still compile, but the compiler will generate a warning.

Let’s change this boolean to an enum class.

enum class LineEnd : bool {
    eNoReturn = false,
    eReturn = true
};
.
.
.
    void print(const char* charString, LineEnd appendReturn) 
    { /* print charString */}
.
.
.
    file1.print("A line", LineEnd::eReturn);
    file2.print("A line, no return", LineEnd::eNoReturn);

Now there is no confusion as to the meaning of the second argument to the File::print method. Also, trying to use values like true or 6 for this argument cause a compiler error.

Update (February 17, 2017): A few days after I published this post, Andrzej Krzemieński published a post with an alternative that uses a tagged_bool class.

Conclusions

The examples in this post are contrived to illustrate the points I am trying to make. I do not expect that anyone would actually try to write the File class I have started to declare because the functionality that would be provided in such a class is already available in classes in the standard C++ library. However, a number of conclusions can be drawn from using the techniques shown in these examples:

  1. By changing function and method arguments from common variable types to classes, including enum classes, a number of errors that formerly would only show up at program execution time can now be caught at compile time. This moves the burden of dealing with these errors from the user to the developer where they belong.
  2. Assuming that the classes and enumeration values are properly named, the meaning of the argument values specified  in the source code are much clearer, thereby making the work of the code maintainer much easier.

User Defined Literals

Units

In my post, Strong Typing or Naked Primitives, I created a Size struct whose constructor takes two arguments, a Width object and a Height object. Here are the definitions:

struct Size final
{
public:
    Size(const Width& w, const Height& h) 
        : m_width(w), m_height(h) {}
    Width getWidth() const noexcept 
        { return m_width; }
    Height getHeight() const noexcept 
        { return m_height;}
private:
    Width m_width;
    Height m_height;
};

class Width
{
public:
	explicit Width(const uint32_t width) 
            : m_width(width) {}
	uint32_t getWidth() const noexcept 
            { return m_width; }
        operator uint32_t() { return m_width; }
private:
	uint32_t m_width;
};

class Height
{
public:
	explicit Height(const uint32_t height) 
            : m_height(height) {}
	uint32_t getHeight() const noexcept 
            { return m_height; }
        operator uint32_t() { return m_height; }
private:
	uint32_t m_height;
};

The arguments in the Width and Height constructors are integers, but what are the units that these integers specify? If you guessed pixels, you win a prize! Well, not really.

When documenting Width and Height, I would mention that the constructor arguments are in units of pixels. Even if you saw

Size size(Width(400), Height(300));

somewhere in source code you had to maintain, you would probably assume that 400 and 300 are in units of pixels. But what about Size size(Width(5), Height(3))? Could you tell without viewing either the documentation or the declaration of Height and Width if the units are pixels? Perhaps the units are inches instead.

I could add a second constructor to Width and Height that took doubles as values and in those constructors, convert the inches values input to pixels. Wait a minute, most of the world deals with metric units not Imperial or US customary units. What if I want to input values in centimetres? I can’t simply add another constructor that takes a double as the number of centimetres because there is already a constructor that takes a double argument.

One potential solution would be to have different classes for each unit, such as WidthInPixels, WidthInInches, WidthInCentimeters, and so forth. So then the Size struct would have constructors like this:

Size(const WidthInPixels& wp, const HeightInPixels& hp);
Size(const WidthInPixels& wp, const HeightInInches& hin);
Size(const WidthInInches& win, const HeightInPixels& hp);
Size(const WidthInInches& win, const HeightInInches& hin);

and so forth. The number of constructors for Size goes up as the square of the number of different units. This would quickly get out control.

A second alternative is to use a tag argument to indicate what the units are:

class Pixels{};
class Inches{};
class Centimeters{};
...
Width(Pixels, const uint32_t pixels) {...};
Width(Inches, const double inches) {...};
Width(Centimeters, const double cm) {...};

Assuming the Width and Height classes also had an operator= operator, you would be prevented from doing this:

Width width = 400;

A third alternative might be to use templates, but that gets even more complicated when you want to return the values in specific units.

None of these alternatives can be viewed as ideal solutions.

User-Defined Literals

For built-in, or common variable types, C++ uses a number of prefix and suffix literals to specify precision. For example:

auto i = 5UL;       / unsigned long
auto j = 3LL;       / long long
auto k = 'x';       / char
auto l = L'x';      / w_char_t
auto m = "one"s;    / string of chars
auto n = 6 + 14.3i; / std::complex
auto o = u32"one";  // UTF-32 encoded string

Wouldn’t it be great if you could have single constructor definitions for Width and Height and specify the units with the values? Something like this:

Height h(500pixels);
Height(3.2inches);
Height(12.1cm);

C++11 introduced user-defined literals and C++14 extended them. Here is the way to define user-defined literals:

ReturnType operator "" _Suffix (Parameters) { /* do something */ };

There are a number of rules and restrictions:

  1. ReturnType can be anything including void.
  2. Suffix must be preceded by an underscore. Literals that do not begin with an underscore are reserved for the literal operators supplied by the standard library.
  3. For C++11, there must be a whitespace between the “” and the underscore. C++14 removed that restriction.
  4. For C++11, the first character of Suffix must be lower case. C++14 removed that restriction. If the Suffix is upper case letters, then there must be no whitespace between “” and the underscore.
  5. For C++11, the Suffix cannot be a reserved word. C++14 removed that restriction. Again, there must be no whitespace between “” and the underscore.
  6. Parameters must be built-in types (like integers, floating-point values, char strings, and so forth).
  7. Integer parameters must be specified as unsigned long long and floating-point parameters as long doubleto ensure that all number types are accepted.
  8. User-defined literal definitions should be placed inside a namespace.
  9. Wherever possible, user-defined literals should be marked as constexpr.

Assuming we want to store the values as pixel values, and that there are 96 pixels per inch (MS Windows), here is how we could define literals and use them in our code:

namespace units {
constexpr uint32_t operator "" _pix(unsigned long long pixels)
{
    return static_cast<uint32_t>(pixels);
}

constexpr uint32_t operator "" _in(long double inches)
{
    assert(inches >= 0.0L);
    return static_cast<uint32_t>(inches * 96.0L);
}
}

using namespace units;
Size size(Width(400_pix), Height(100_pix + 3.0_in));
Size size2(Width(350), Height(200));

Notes:

  1. Because the user-defined literals are defined in a namespace above (units namespace), the using namespace units; line is required. You cannot preface the user-defined literal with the namespace name. For example:
    Size size(Width(400units::_pix), Height(100units::pix + 3.0units::_in))

    will not compile.

  2. Width and Height still accept uint32_t values as arguments. This is useful when accepting pixel values from other variables. For example:
    wxSize wxS = ...;
    Size size(wxS.GetWidth(), wxS.GetHeight());

Pros

  1. Values can be specified in different units.

Cons

  1. Use of user-defined literals is limited to constant values. That is the reason I included constexpr in the definitions above. You cannot use them with variables. For example, the following will not compile:
    uint32_t width = 200;
    Size size(Width(width_pix), Height(4.0_in));

    though the following does compile and provide the desired result:

    uint32_t width = 200_pix;
    uint32_t height = 4.0_in;
    Size size(Width(width), Height(height));
  2. User-defined literals are limited to use as suffixes; they cannot be used as prefixes. That is:
    uint32_t width = _pix200;
    uint32_t height = _pix(200);

    both will not compile.

This post has just scratched the surface of user-defined literals.
See the references included in Additional Information, below,for more information on user-defined literals, their limitations, and more examples.

Additional Information

  1. User-defined literals
  2. Modern C++ Features – User-Defined Literals
  3. User defined literals – Part 1, Part 2, Part 3
  4. User defined literals
  5. User-Defined Literals (C++)