0

I am not sure if I asked the most clear question so forgive me if it comes off as opinion based.

In programming, I have generally followed the philosophy that if a library belongs in the .cpp file, then it should belong in the .hpp file. However, this does not necessarily apply when it comes to OOP. I came up with a very small example:

Class1 Source:

#include "class1.hpp"

int Class1::getOther(Class2 *obj)
{
  return obj->get();
}

void Class1::setOther(Class2 *obj, int val)
{
  obj->set(val);
}

Class1 Header:

#ifndef CLASS_1_HPP
#define CLASS_1_HPP

#include "class2.hpp"

class Class1
{
  public:
    int getOther(Class2 *obj);
    void setOther(Class2 *obj, int val);

    int get() const { return value; }
    void set(int val) { value = val; }
  private:
    int value;
};

#endif

Class2 Source:

#include "class1.hpp" // Placed here instead of class2.hpp
#include "class2.hpp"

int Class2::getOther(Class1 *obj)
{
  return obj->get();
}

void Class2::setOther(Class1 *obj, int val)
{
  obj->set(val);
}

Class2 Header:

#ifndef CLASS_2_HPP
#define CLASS_2_HPP

class Class1; // Forward Declaration

class Class2
{
  public:
    int getOther(Class1 *obj);
    void setOther(Class1 *obj, int val);

    int get() const { return value; }
    void set(int val) { value = val; }
  private:
    int value;
};


#endif

Main:

#include <iostream>

#include "class1.hpp"

int main()
{
  Class1 *A = new Class1;
  Class2 *B = new Class2;

  A->setOther(B,15);
  A->set(A->getOther(B));

  std::cout << "A: " << B->getOther(A) << " B: " << A->getOther(B) << std::endl;

  delete A;
  delete B;
}

This is a simple example and not necessarily meant to depend on each other. However, in order to get this code to compile, we have to move class1.hpp from class2.hpp, to class2.cpp. And then to get around it, we create a forward declaration of Class1 in Class2. This get's rid of the cyclic dependency as the two headers do not clash, and Class1 and Class2 will now know about each other safely.

When I program, I consider both the header and source file as one entire object. Or rather that source file is just an extension of the header file and expands all of the definitions of the header file. What I would do in this case, is create a driver class that know's about both Class1 and Class2 and controls them.

How is this implementation avoiding the cyclic dependencies? And how does the compiler compile this and not catch any possible cyclic dependencies? And lastly, when is it necessary to use forward declarations in this manner?

Sailanarmo
  • 996
  • 8
  • 29
  • I think @NathanOliver answers the main meat of my question. Is there a way to move the dup to his link instead? Also, another question which I think is opinion based. Is using Forward Declarations a sign that you should rethink your design? – Sailanarmo Apr 16 '19 at 20:54
  • 1
    Unrelated: Prefer `Class1 A;` to `Class1 *A = new Class1;`. In general, [Why should C++ programmers minimize use of 'new'?](https://stackoverflow.com/questions/6500313/why-should-c-programmers-minimize-use-of-new) – user4581301 Apr 16 '19 at 20:54
  • @Sailanarmo I've taken care of it for you. – NathanOliver Apr 16 '19 at 20:55
  • @Sailanarmo Circular dependency should raise suspicions but is not necessarily bad design. It should cause you to stop for a second and consider why there is a circular dependency. It would be hard to justify a design like the one presented here without understanding the purpose of those types. – François Andrieux Apr 16 '19 at 20:57
  • @user4581301 yes, I normally wouldn't do it this way. I would pass the classes by reference to entirely avoid creating new. However I was thinking about the QT Implementations. – Sailanarmo Apr 16 '19 at 21:00
  • @FrançoisAndrieux I guess this is a fair enough response. The current program that I am working on has a circular dependency in it somewhere. And I have resorted to trying to physically draw on a white board what is included where. I am finding that this project has a lot of forward declarations in it and it is making it very difficult for me to find where the circular dependency is. – Sailanarmo Apr 16 '19 at 21:04
  • @NathanOliver thank you! – Sailanarmo Apr 16 '19 at 21:04
  • It depends on the stage of the project. If you have an established library, it sure is nice when everything necessary is in the header file. However, if you have a project in active development with lots of changes... When you start working with big projects--like thousands, tens of thousands, or more files--then you want to put the least amount necessary to compile in the header file sometimes. Because every change in a header can cause a recompile even if a file doesn't actually use the file being modified. Sometimes I use forward declarations and references/pointers to lessen dependencies. – Joseph Willcoxson Apr 16 '19 at 21:05
  • 3
    @Sailanarmo Forward declarations have the benefit that they can limit the number of files that need to be recompiled when you change a header. They may not all represents fixes for circular dependencies. In some organisations, forward declarations are strongly encouraged for this reason. – François Andrieux Apr 16 '19 at 21:06

0 Answers0