5

I am attempting to forward declare some variables used only privately in a class to limit the number of headers that I have to include when using this one.

Sadly, the class that I want to forward declare has turned out to be a typedef, and it's a 3rd party library that I can't edit (let's call it "boost::asio::strand" for the sake of argument)

This question Forward declaration of a typedef in C++ Demonstrates that the only solutions are either:

  • Just include the header and accept it's not possible
  • Forward declare what would be typedef'ed and add my own typedef

Looking at the second solution, is there any way that I can protect myself from the typedef changing in the library so that the compiler complains about the typedef rather than the usage of an undefined type when the class is removed/renamed and make it less of a maintenance headache?

UKMonkey
  • 6,941
  • 3
  • 21
  • 30
  • 4
    Use a pimpl?͏͏͏͏͏͏͏͏ – wally May 23 '18 at 12:34
  • 1
    @wally While it does work and certainly removes the need for the including of the object, it's not what I want to do because it means that rather than have a (for example) private `std::vector` I now need a `pimpl*` which contains the `std::vector` The concept of C & C++ is that you don't pay for what you don't use, and there's a cost of this extra object that I don't need. – UKMonkey May 23 '18 at 13:08
  • Is the third party library within a namespace? – Passer By May 23 '18 at 13:19
  • @PasserBy yes. We can call this namespace "boost" for sake of argument. – UKMonkey May 23 '18 at 13:21
  • 1
    You know, after an attempt at an answer, I don't think there is enough information here. Why do you only need a forward declaration? Do you plan to use it in your public interface? In that case, your user will still include the header. Do you only use it privately? Then why can you work with only a forward declaration at all? – Passer By May 23 '18 at 13:32
  • @PasserBy no; it's private only; so the header gets included in the cpp file. The pimpl idea removes the need to forward declare anything in the 3rd party lib; which isn't a bad solution, but it's not the perfect one as there shouldn't be any need for a pimpl. If the class wasn't typedef'ed then I'd just forward declare it without issue. The fact that it IS typedef'ed though means that I can't forward declare it trivially. – UKMonkey May 23 '18 at 13:37
  • If the typedef involves namespace `boost::detail` in any way, DO NOT attempt "the second solution". Otherwise it might be okay, but really I'd just include the correct header. – aschepler May 23 '18 at 13:38
  • I mean, I don't understand how can you use it privately with only a forward declaration. Do you have a pointer/reference as member? In that case, use whatever pointer/reference type and cast it in your implementation – Passer By May 23 '18 at 13:41
  • IMHO he just doesn't want to include boost headers to his header, he just includes boost later to the source file... I've recently solved similar issue and unfortunately using pimpl was the only solution I found :( – Jaa-c May 23 '18 at 13:42
  • @Jaa-c that's exactly it; and looking at the solution that I ended up with it's a pimpl (though I hadn't realised it until wally's comment). The only alternative I can think of is to use a `void*` so that I don't need to forward declare at all ... but I think I'd rather jump in a hornets nest. – UKMonkey May 23 '18 at 13:54

1 Answers1

0

I would try not to rely on the forward declaration of the original class at all if possible. I may have missed a case but I think a forward declaration is only useful for a purely private use if the type appears in a method signature somehow, or if your class contains a member that somehow points to or references the type, possibly indirectly.

I propose to to forward declare a wrapper for the class and only define that in the implementation file, when the actual class or typedef is known.

Say your class header currently looks like this:

// need header because it contains UglyTypeDef:
#include <UglyHeader.h>

class MyClass {
public:
  int theMethod(int a);
private:
  void uglyMethod(UglyTypeDef &utd);

  int someMember;
  UglyTypeDef *utdp;
  std::vector<UglyTypeDef *> utds;
};

In this example we could do with a forward declaration, but we do not want to rely on the internals of UglyHeader.

I would change MyClass like this:

class MyClass {
public:
  int theMethod(int a);
private:
  // move the private method into the implementation file   

  // hide the ugly typedef
  // we safely forward declare our own private wrapper
  struct UglyTypeDefWrapper;

  int someMember;
  UglyTypeDefWrapper *utdp;
  std::vector<UglyTypeDefWrapper *> utds;
};

Now in order to make this work, the implementation in the cpp file must change as well:

#include "MyClass.hpp"
#include <UglyHeader.h>

struct MyClass::UglyTypeDefWrapper {
   // if possible save another level of indirection 
   // and store by value, otherwise use a second indirection
   // by cleverly wrapping at the right level of abstraction 
   // this abstraction can be free in many cases
   UglyTypeDef td;
};

namespace {
  // we completely hide the private method in this file as
  // an implementation detail and pass it whatever it needs
  // this also helps the compiler to inline it, 
  // because it knows it cannot be referenced in 
  // a different compilation unit
  // we need to pass all members as needed
  void uglyFormerMethod(int &someMember, UglyTypeDef &utd) {
    someMember += utd.whatever();
  }
}

int MyClass::theMethod(int a) {
  utd->td.doWhatever();
  uglyFormerMethod(someMember, *utd);
  for(auto utdwp: utds) {
    utdwp->td.doIt();
  }
}
PaulR
  • 3,587
  • 14
  • 24