Dependency Inversion Principle in C++ is the fifth & last design principle of a series SOLID as a Rock design principles. The SOLID design principles focus on developing software that is easy to maintainable, reusable & extendable. In this article, we will see an example code with the flaw & correct it with help of DIP. We will also see guideline & benefits of DIP inclosure of the article.
By the way, If you haven’t gone through my previous articles on design principles, then below is the quick links:
  1. SRP – Single Responsibility Principle
  2. OCP – Open/Closed Principle
  3. LSP – Liskov Substitution Principle
  4. ISP – Interface Segregation Principle
  5. DIP – Dependency Inversion Principle
The code snippets you see throughout this series of articles are simplified not sophisticated. So you often see me not using keywords like override, final, public(while inheritance) just to make code compact & consumable (most of the time) in single standard screen size. I also prefer struct instead of class just to save line by not writing “public:” sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons.
Note:

Intent

1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
2. Abstractions should not depend on details. Details should depend on abstractions.

What are the High-level & Low-level modules?

High-level modulesdescribes operations which is more abstract in nature & contain more complex logic. These modules orchestrate low-level modules in our application.
Low-level modulesdescribes implementations more specific & individual to components focusing on details & smaller parts of the application. These modules are used inside the high-level modules.

Motivation: Violating Dependency Inversion Principle

enum class Relationship { parent, child, sibling };

struct Person {
    string      m_name;
};

struct Relationships {      // Low-level <<<<<<<<<<<<-------------------------
    vector<tuple<Person, Relationship, Person>>     m_relations;

    void add_parent_and_child(const Person &parent, const Person &child) {
        m_relations.push_back({parent, Relationship::parent, child});
        m_relations.push_back({child, Relationship::child, parent});
    }
};

struct Research {           // High-level  <<<<<<<<<<<<------------------------
    Research(const Relationships &relationships) {
        for (auto &&[first, rel, second] : relationships.m_relations) {// Need C++17 here
            if (first.m_name == "John" && rel == Relationship::parent)
                cout << "John has a child called " << second.m_name << endl;
        }
    }
};

int main() {
    Person parent{"John"};
    Person child1{"Chris"};
    Person child2{"Matt"};

    Relationships relationships;
    relationships.add_parent_and_child(parent, child1);
    relationships.add_parent_and_child(parent, child2);

    Research _(relationships);

    return EXIT_SUCCESS;
}

Solution: Example of Dependency Inversion Principle in C++

struct RelationshipBrowser {
    virtual vector<Person> find_all_children_of(const string &name) = 0;
};

struct Relationships : RelationshipBrowser {     // Low-level <<<<<<<<<<<<<<<------------------------
    vector<tuple<Person, Relationship, Person>>     m_relations;

    void add_parent_and_child(const Person &parent, const Person &child) {
        m_relations.push_back({parent, Relationship::parent, child});
        m_relations.push_back({child, Relationship::child, parent});
    }

    vector<Person> find_all_children_of(const string &name) {
        vector<Person> result;
        for (auto &&[first, rel, second] : m_relations) {
            if (first.name == name && rel == Relationship::parent) {
                result.push_back(second);
            }
        }
        return result;
    }
};

struct Research {                                // High-level <<<<<<<<<<<<<<<----------------------
    Research(RelationshipBrowser &browser) {
        for (auto &child : browser.find_all_children_of("John")) {
            cout << "John has a child called " << child.name << endl;
        }
    }
    //  Research(const Relationships& relationships)
    //  {
    //    auto& relations = relationships.relations;
    //    for (auto&& [first, rel, second] : relations)
    //    {
    //      if (first.name == "John" && rel == Relationship::parent)
    //      {
    //        cout << "John has a child called " << second.name << endl;
    //      }
    //    }
    //  }
};

Yardstick to Craft Dependency Inversion Principle(DIP) Friendly Software in C++

Benefits

Reusability

Maintainability

Conclusion

As you can see we took a basic example of code & converted it into a reusable, flexible & modular piece of code. If I would have to summarize DIP in simple & short sentence then it would be like:
Do not use the concrete object directly unless you have a strong reason to do so. Use abstraction instead.
DIP trains us to think about classes in terms of behavior, rather than construction or implementation.