Pointers

Pointer Ownership in Complex Hierarchies

What are some strategies for managing pointer ownership in complex object hierarchies?

3D character art

Managing pointer ownership in complex object hierarchies can be challenging, but there are several strategies you can employ to make it more manageable and less error-prone. Let's explore some of these strategies:

Use Smart Pointers

Smart pointers are the cornerstone of modern C++ memory management. They help clarify ownership semantics and prevent memory leaks.

std::unique_ptr for Exclusive Ownership

Use std::unique_ptr when an object should have only one owner:

#include <iostream>
#include <memory>

class Weapon {
 public:
  Weapon(std::string name)
  : mName{std::move(name)} {}
  std::string mName;
};

class Character {
 public:
  Character(std::string name)
  : mName{std::move(name)} {}
  void equip(std::unique_ptr<Weapon> weapon) {
    mWeapon = std::move(weapon);
  }
  std::string mName;
  std::unique_ptr<Weapon> mWeapon;
};

int main() {
  auto character = std::make_unique<Character>(
    "Hero");
  character->equip(std::make_unique<Weapon>(
    "Sword"));
  std::cout << character->mName
    << " equipped "
    << character->mWeapon->mName  << "\n";
}
Hero equipped Sword

std::shared_ptr for Shared Ownership

Use std::shared_ptr when multiple objects might own a resource:

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

class Item {
 public:
  Item(std::string name)
  : mName{std::move(name)} {}
  std::string mName;
};

class Inventory {
 public:
  void addItem(std::shared_ptr<Item> item) {
    mItems.push_back(item);
  }
  std::vector<std::shared_ptr<Item>> mItems;
};

class Character {
 public:
  Character(std::string name)
  : mName{std::move(name)} {}
  Inventory mInventory;
  std::string mName;
};

int main() {
  auto hero = std::make_unique<Character>(
    "Hero");
  auto companion = std::make_unique<Character>(
    "Companion");

  auto sharedItem = std::make_shared<Item>(
    "Magic Potion");
  hero->mInventory.addItem(sharedItem);
  companion->mInventory.addItem(sharedItem);

  std::cout << "Item count: "
    << sharedItem.use_count() << "\n";
}
Item count: 3

Implement Clear Ownership Policies

Establish clear rules about who owns what in your object hierarchy:

class Game {
 public:
  void addCharacter(
    std::unique_ptr<Character> character
  ) {
    mCharacters.push_back(std::move(character));
  }
  std::vector<std::unique_ptr<
    Character>> mCharacters;
};

int main() {
  Game game;
  game.addCharacter(std::make_unique<Character>(
    "Hero"));
  game.addCharacter(std::make_unique<Character>(
    "Villain"));
}

In this example, Game clearly owns all Character objects.

Use Weak Pointers to Break Circular References

std::weak_ptr can be used to break circular references that might occur with std::shared_ptr:

#include <iostream>
#include <memory>

class Character;

class Weapon {
 public:
  Weapon(std::string name)
  : mName{std::move(name)} {}
  void setOwner(std::shared_ptr<Character> owner) {
    mOwner = owner;
  }
  std::weak_ptr<Character> mOwner;
  std::string mName;
};

class Character {
 public:
  Character(std::string name)
  : mName{std::move(name)} {}
  void equip(std::shared_ptr<Weapon> weapon) {
    mWeapon = weapon;
    weapon->setOwner(shared_from_this());
  }
  std::shared_ptr<Weapon> mWeapon;
  std::string mName;
};

int main() {
  auto character = std::make_shared<Character>(
    "Hero");
  auto weapon = std::make_shared<Weapon>(
    "Sword");
  character->equip(weapon);

  std::cout << "Character use count: "
    << character.use_count() << "\n";
  std::cout << "Weapon use count: "
    << weapon.use_count() << "\n";
}

Consider Using the PIMPL Idiom

The PIMPL (Pointer to Implementation) idiom can help manage complex hierarchies by hiding implementation details:

// Character.h
class Character {
 public:
  Character(std::string name);
  ~Character();
  Character(Character&&) noexcept;
  Character& operator=(Character&&) noexcept;
  void equip(std::string weaponName);

 private:
  class Impl;
  std::unique_ptr<Impl> pImpl;
};

// Character.cpp
#include <vector>

#include "Character.h"

class Character::Impl {
 public:
  std::string name;
  std::vector<std::unique_ptr<Weapon>> weapons;
};

Character::Character(std::string name)
: pImpl{std::make_unique<Impl>()} {
  pImpl->name = std::move(name);
}

Character::~Character() = default;
Character::Character(
  Character&&) noexcept = default;
Character& Character::operator=(
  Character&&) noexcept = default;

void Character::equip(std::string weaponName) {
  pImpl->weapons.push_back(
    std::make_unique<Weapon>(std::move(weaponName)
  ));
}

Use Factory Functions

Factory functions can help enforce ownership policies:

class GameWorld {
 public:
  Character* createCharacter(std::string name) {
    auto character = std::make_unique<Character>(
      std::move(name));
    Character* rawPtr = character.get();
    mCharacters.push_back(std::move(character));
    return rawPtr;
  }

 private:
  std::vector<
    std::unique_ptr<Character>> mCharacters;
};

int main() {
  GameWorld world;
  Character* hero = world.createCharacter("Hero");
  // World owns the Character, but we can
  // still use the raw pointer
}

By using these strategies, you can create clear ownership semantics in your complex object hierarchies, reducing the risk of memory leaks and making your code more maintainable.

Remember, the key is to be consistent in your approach and to document your ownership policies clearly for other developers who might work with your code.

Answers to questions are automatically generated and may not have been reviewed.

3D art showing a progammer setting up a development environment
Part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

This course includes:

  • 60 Lessons
  • Over 200 Quiz Questions
  • 95% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

View Course
Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2025 - All Rights Reserved