C++ header files and inter-class connections
There are some interesting implications to the way that you have to handle references to other bits of code when you write a C++ header file. There are things you can do which turn out to be suboptimal, and until or unless you work someplace with a coding style document or get some mentor who's already been there, you're likely to make some mistakes.
There's this thing we tend to do when we need a class to own an instance of another class. Maybe that class is going to do some work for us, or will hold state for us, or maybe it's expensive to set up, so we want it to hang around for a while. Who knows, right?
Anyway, a common way to do this is to set up the outer class such that it has a pointer to the inner one. You *could* have it just own it directly, but then that introduces problems.
That is, you don't want this (in your foo.h, inside the Foo class):
private: Bar bar_;
What kind of problems? Well, for one thing, you now have to #include bar.h in your foo.h at that point, since it's "really there". The compiler needs to know how big a Bar is at the point it's dealing with the rest of your Foo class. It can only do that if you #include it.
Of course, bar.h probably has its own dependencies which get #included, and so now you end up with this evil web of slow compiles. It sucks.
* ... Scooby-Doo wavy lines... *
Hey, it's me from two days after this post was originally written. It turns out that's not actually true. You *can* in fact pass in arguments even when the Bar is "baked in" to your class. You just have to do it when you get constructed, like this:
Foo::Foo(int something) : bar_(something) { ... }
So there you go. You can do it that way if you want. Obviously, you need to know exactly how you intend to construct it fairly early on in your lifetime. If that isn't known until later on, you're still kind of screwed, and wind up doing something else like one of the later methods.
I'm doing an inline update here and striking through the old stuff so (1) you can see my mistake (2) I can own it and (3) nobody gets the wrong idea from my mistake sitting out in the open forever.
Thanks to Jens for the first comment reporting this. No thanks to anyone posting in the usual places calling me stupid for not knowing this. It's like, no shit, I don't know everything. Good thing THE ONE does.
* ... Scooby-Doo wavy lines out ... *
What used to happen next is that someone would say, okay, let's make it a pointer instead, and this would be their next iteration:
private: Bar* bar_;
This by itself meant you now have to call new on it somewhere in the actual body of the class to create it, and then you also had to call delete to release it too! Otherwise, you'd leak it any time someone deleted *you*. But, hey, now you could send arguments to its constructor, since they'd get jammed in when you did the "new".
The switch to a pointer here did allow one other neat trick: you could now drop the #include of bar.h and just forward-declare it, like this:
class Bar;
(And yes, you might've had to wrap it in some namespace { ... } stuff if it wasn't in the same namespace as you.)
That's nice, but of course now you have the manual memory management thing going on, and that truly sucks. For a long time, that was the best we could do, then people started rigging their own smart pointer wrapper classes. I picked up "scoped_ptr<>" from my employer at the time, and after leaving that company, rigged it up in my own stuff at home. I used that for years until C++ evolved a bit more and eventually gained unique_ptr and shared_ptr. Then I switched to those.
This means we can all collectively do this now:
private: std::unique_ptr<Bar> bar_;
You have to #include <memory> for the unique_ptr itself, but you still don't need to bring in bar.h in your header file, so the compiles stay relatively uncomplicated. You get automatic destruction when you go out of scope, so that's nice too. You just have to wrap your head around not doing stupid things with the lifetimes of your stuff, same as always. (If you're using .get() on a unique_ptr and old C library stuff isn't involved somewhere, WTF, over.)
Another side-effect of coming forward a version or two in the C++ standards is that you get things like std::make_unique and std::make_shared, so there will be no more of this "call new directly" crap. This means you can now put "new" on the list of things that your linter can scream about, and start banning it from the code base.
That's pretty much where things are now: we do these smart pointers because we want things to suck less than bare pointers, and we do pointers at all because it avoids this whole multi-suck thing of #include hell, terrible compile hell, and default constructor hell. (Clearly, C++ programmers have many hells.)
Honestly, it's kind of stupid that you have to do any of this, but when you remember the history of how things got to this point, it's kind of understandable. It still sucks but at least you can tell how it happened.
It would be a lot better if this kind of stuff was handled some other way that didn't make you deal with so much of the housekeeping. It's like, yes, class Foo is going to hang onto a Bar. That instance of Bar needs to exist as long as Foo does. We might want to configure that Bar somehow. Oh, and we might even need to *swap out* the Bar somehow.
Yep, that's the last part here: once you moved from "a direct Bar in your class" to a pointer and beyond, you got the ability to replace it with something similar. The usual reason for this is when you're doing a mock, like with gmock. You inherit from Bar and make a MockBar, and it has all of the right curves and angles in the right places, so it just "fits" where everything else is expecting a Bar.
I've been down this road many times. It usually involves creating the outermost class, then reaching into it from inside the test (hello, private member access) to change some of the smart pointers to point at a MockWhatever I had created in the test class. This was back in the days of using scoped_ptr so we didn't have the make_whatever yet. It looked like this:
Foo foo; // the thing we're testing MockBar* mock_bar = new MockBar(...) // what we're injecting foo.bar_.reset(mock_bar); // and here we inject it
Since the Foo constructor usually created a Bar for itself, this meant you'd build one and immediately toss it out. If Bar's constructor didn't actually do any work, this was relatively safe (and quick). Bar's constructor might've been written like Foo's constructor which just creates instances of classes it intends to use later. There were classes which did non-trivial work in their constructors (fools!) and so all of this got very costly. Tests would run for *multiple seconds* instead of mere milliseconds (or less).
You'd end up needing to some truly crazy things to work around this. One way was to change your code, and so now Foo's constructor would do *nothing*, and then Foo::Init would actually create things it needed (like a Bar). This also meant you couldn't really test Foo::Init, since it would go off and create those real versions of the objects and you couldn't intercept them.
Another way was to have multiple constructors: one that normal people called which did all of the sub-class creation stuff, and another one that was private (again, hello to the friend class stuff) that accepted a Bar and whatever else you cared to pass in. Then your test could just "slip in the back door" to create a Foo. This did mean your actual constructor went untested unless you went to lengths to do that directly, and then that generally wasn't great, either.
Then there was still another method where Foo would have a function like MakeBar() which would *create a Bar on the spot* and would return it to the caller. But, instead of calling it directly, you'd then have a glorified function pointer referencing it. We didn't have std::function at that point, but the company had its own magic called Closures and Callbacks, so you'd do it that way.
It looked a little like this:
Foo::Foo() : get_bar_(MakeBar) {} Bar* Foo::MakeBar(...) { return new Bar(...); }
Then, in Init (or whatever part of the code needed a Bar), it would do this:
bar_ = get_bar_();
get_bar_, then, was the glorified function pointer.
So, still with me? What happened next is that the test class would *reach into Foo* (private access yadda yadda) and would reset its get_bar_ pointer to instead call something inside the test class. Yes, really.
Foo foo; foo.get_bar_ = NewPermanentCallback(TestClass::MakeMockBar);
[Don't hold me to the exact syntax of their NewPermanentCallback stuff. It's been over a decade, okay? Just think "it created a thing that would call another thing".]
Now you could call Foo::Init, and it would call its local get_bar_ pointer which would then follow the redirection into the test class. The test class would return a pointer to the MockBar it had created for the occasion.
This was seriously annoying, but it was pretty much all you could do when you had code that created instances of things "mid-stream" (like within a member function) and you needed to mock them out. That is, if you had Foo::DoStuff that created a KernelInstaller and kept it local to its own scope, you couldn't do any of the top-level stuff described earlier. Your only hope for injecting a mock was to rework it to call a thing that generated a KernelInstaller, and then you'd intercept *that*.
Awful, right? Just describing it is a chore. Doing it was worse.
I haven't even gotten into the realm of whatever inefficiencies you'd pick up by bouncing through a smart pointer instead of doing something else. I'm sure it exists and matters on some level, and there are undoubtedly people who have to care about it. I've managed to not have to worry about that quite yet. I don't want to discount it as a problem because it surely matters to someone out there, but handling it is an even bigger problem.
One related thing to the whole Mock injection situation is that you have to make everything in Bar "virtual" so that it can be overridden by the MockBar child class. That's another potential layer of indirection with whatever performance impact that might add.
Apparently, if that's your world (it's not mine...) then if you're going to "inject a mock", you end up having a MockBar which looks *EXACTLY* like a Bar, and then you do some build system trickery to wrangle that one into place when you go to build your test.
That is, in normal life, foo.o and bar.o turn into server_bin, but in test life, foo.o and mock_bar.o turn into test_foo. The obvious problem with this is that you have to be very careful about not breaking that interface. Maybe mock_bar.cc would #include bar.h instead of having a lookalike mock_bar.h? Also, it would still have to be *named* Bar inside mock_bar.cc, because that's what the linker is going to be looking for.
At the employer in question, they called this scenario the "high-performance mock", and I am very happy that I never had to use it.
So, taken all together, I like to think of this body of knowledge as an "invisible hand" (in the economics sense) that reached backwards in time and influenced a great many design decisions. If you intended to test a bunch of things thoroughly, you had to start from a bunch of design elements that left the door open for you later. Trying to retrofit something in order to make it testable later was anything but fun.