0

I have created a C++ module which has an API part and an internal part. The API contains a long (more then 30) list of polymorphic classes inheriting directly or indirectly from the same baseclass. In my internal classes/functions these classes are used differently, so I moved some functionality to these classes to be used polimorphically. These functionalites depend on some internal resources, while the internal functions depend on these classes so I broke this circular dependency by forward declaring the necessary classes in the .h files and including them in the .cpp files. This way my module compiles and works perfectly.

Here comes the problem. When I include the API from another module, I get "undefined reference to ...." errors to internal functions in the API classes polimorphic functions. I think this is due to the forward declarations. These internal classes are never defined in the eyes of the second module which uses the API.

Broadly my module looks like this:

InternalClass.h:

class ApiObjBase;
class InternalClass {
public:
  static InternalClass& get(); // singleton

  void processApiObjBase(ApiObjBase& apiObj);

  static const Resource& getResource1();
  static const Resource& getResource2();
  static const std::map<uint64_t, std::unique_ptr<ApiObjBase>>& getApiObjStorage();

  UacManager(const UacManager&) = delete; // singleton
  UacManager& operator=(const UacManager&) = delete; // singleton
private:
  InternalClass(); // singleton

  Resource resource1;
  Resource resource2;

  std::map<uint64_t, std::unique_ptr<ApiObjBase>> ApiObjStorage;
}

InternalClass.cpp:

#include "ApiObjBase.h"

void InternalClass::processApiObjBase(ApiObjBase& apiObj) {
  apiObj.internalPolyFunc1();
  apiObj.internalPolyFunc2();
  // ...
}

const Resource& InternalClass::getResource1() {
  return get().resource1;
}

const Resource& InternalClass::getResource2() {
  return get().resource2;
}

// Other implementations of member functions ...

ApiObjBase.h:

class InternalClass;
class ApiObjBase {
protected:
  int internalVariable1;
  int internalVariable2;
  
  int apiVariable1;
  int apiVariable2;

public:
  ApiObjBase() = default;

  // getters, setters

  void internalPolyFunc1() = 0;
  void internalPolyFunc2() = 0;

  void apiPolyFunc1() = 0;
  void apiPolyFunc2() = 0;
}

ApiObjBase.cpp:

#include "ApiObjBase.h"
// ...

ApiObjDerived1.h:

#include "ApiObjBase.h"
class ApiObjDerived1 : public ApiObjBase {
protected:
  int internalVariable3;
  int internalVariable4;
  
  int apiVariable3;
  int apiVariable4;

public:
  ApiObjDerived1() = default;

  // getters, setters

  void internalPolyFunc1() override;
  void internalPolyFunc2() override;

  void apiPolyFunc1() override;
  void apiPolyFunc2() override;
}

ApiObjDerived1.cpp:

#include "ApiObjDerived1.h"
#include "InternalClass.h"
void ApiObjDerived1::internalPolyFunc1() {
  Resource& res = InternalClass::getResource1(); // 
  // ...
}

void ApiObjDerived1::internalPolyFunc2() {
  Resource& res = InternalClass::getResource1();
  // ...
}

void ApiObjDerived1::apiPolyFunc1() {
  // ...
}

void ApiObjDerived1::apiPolyFunc2() {
  // ...
}
// ...

ApiObjDerived2.h:

#include "ApiObjBase.h"
class ApiObjDerived2 : public ApiObjBase {
protected:
  int internalVariable5;
  int internalVariable6;
  
  int apiVariable5;
  int apiVariable6;

public:
  ApiObjDerived2() = default;

  // getters, setters

  void internalPolyFunc1() override;
  void internalPolyFunc2() override;

  void apiPolyFunc1() override;
  void apiPolyFunc2() override;
}

ApiObjDerived2.cpp:

#include "ApiObjDerived2.h"
#include "InternalClass.h"
void ApiObjDerived2::internalPolyFunc1() {
  Resource& res = InternalClass::getResource2();
  // ...
}

void ApiObjDerived2::internalPolyFunc2() {
  // ...
}

void ApiObjDerived2::apiPolyFunc1() {
  // ...
}

void ApiObjDerived2::apiPolyFunc2() {
  // ...
}
// ...

SomeOtherModule.cpp:

#include "ApiObjDerived2.h"
void main(int argc, char** argv) {
  std::unique_ptr<ApiObjBase> apiObj = std::make_unique<ApiObjDerived2>();
  apiObj.get()->apiPolyFunc1();

  return EXIT_SUCCESS;
}

For this example the error would the following while linking SomeOtherModule:

(...) In function `ApiObjDerived2::internalPolyFunc1()': 
(...) undefined reference to `InternalClass::getResource2()'

My only idea to this problem is to create wrapper classes (ApiObjBaseWrapper, ApiObjDerived1Wrapper, ApiObjDerived2Wrapper) for each polymorphic class and move all internal member variables and functions to them. These wrapper classes would contain the original derived class as a private member variable, which could be accessed by a virtual getter which returns a reference or a pointer to ApiObjBase. This would separate the internal and API part of the module making predeclarations unnecessary in the API, while maintaining all the advantages of polymorphism. After this, InternalClass could use ApiObjBaseWrapper, and SomeOtherModule could continue to use ApiObjBase.

My problem with this idea is that this would require a LOT of new files, classes and code (I would have to create another ~30 classes with .cpp and .h files). I cannot emphasize more how big this module is.

What do you suggest, what should I do?

Sylvester
  • 91
  • 3
  • 11
  • 1
    At what point were `ApiObjDerived1.cpp` and its similar brethren in your code compiled *and* linked into your final program? Undefined references are *usually* about skipping the latter of those in your final build target. See this Q&A: [What is an undefined reference/unresolved external symbol error and how do I fix it?](https://stackoverflow.com/questions/12573816/what-is-an-undefined-reference-unresolved-external-symbol-error-and-how-do-i-fix) – WhozCraig Sep 11 '20 at 15:19
  • 1
    So? Where is InternalClass::getResource2 defined? – user253751 Sep 11 '20 at 15:21
  • In ```InternalClass.cpp``` I'll edit my example code at once. – Sylvester Sep 11 '20 at 15:26
  • *"When I include the API from another module"* -- how did you include the API? Since your module has both header and source files, it should be included in two steps: 1) `#include` the header files, and 2) link against the compiled source files. Did you do both steps? – JaMiT Sep 11 '20 at 15:26
  • Yes, I did both. – Sylvester Sep 11 '20 at 15:30
  • 1
    How do you compile your code? I think your compiler is unawere it has to compile some files such as ApiObjDerived2.cpp – tuket Sep 11 '20 at 15:35
  • I use a Makefile to link the ```*.o``` files together. It also creates a ```lib*.a``` library file out of ```ApiObj*.o``` object files. The Makefile of ```SomeOtherModule``` depends on this ```lib*.a``` file as an external library. – Sylvester Sep 11 '20 at 15:54
  • Show the exact compilation commands where `InternalClass.cpp` gets compiled and `InternalClass.o` gets put in the lib. – Ted Lyngmo Sep 11 '20 at 15:55
  • It might help to simplify your example. Start with just your base class, trimmed down to a single member function (no data). For now, make that function non-virtual and implement a dummy body in the source file. If you can compile with that, add `InternalClass` to the mix (again, trimmed down to a single member function) and have the base class' function call `InternalClass::getResource2()`. Most likely, that will be enough to reveal the problem; the mess of inheritance should not be a contributing factor. – JaMiT Sep 11 '20 at 15:56
  • I'll try these later. Thank you! – Sylvester Sep 11 '20 at 16:28

0 Answers0