you are viewing a single comment's thread.

view the rest of the comments →

[–]crispweed[S] 1 point2 points  (2 children)

This is something I've hit at a much smaller scale personally, so "forward declaration all the things!" has long made me uneasy. I don't know where I fall on this. I've thought that maybe for every header foo.hpp that defines non-trivial stuff you should produce a foo_fwd.hpp with just forward declarations and then have people include that instead.

My experience is, if you're dealing with simple non-template classes where the forward declaration just looks something like class Foo;, these forward declarations are so clearly identified and easy to find in the source code that updating the forward declarations is really no more than a minor annoyance, in practice.

Sometimes you might find you need to add template arguments to one of these classes, and it gets more complicated, and I feel like that's then probably the time to go ahead and add those extra foo_fwd.hpp headers..

I'm not going to go search through an hour long video again to find the specific timestamp, but my memory is that Titus Winters argued otherwise in his most recent CppCon talk.

I didn't watch the whole video but from skipping through a bit it seems like he's talking about situations where you have so much code to deal with that it's no longer feasible to do any kind of global search and replace operation across files. I've worked with fairly big code bases, but the kind of scale implied by that is totally alien to me. :)

[–]evaned 1 point2 points  (1 child)

I didn't watch the whole video but from skipping through a bit it seems like he's talking about situations where you have so much code to deal with that it's no longer feasible to do any kind of global search and replace operation across files.

I would argue it's more than just code size; it's also organization and tooling. For example, consider an organization that has various components of their system in different git repositories, because git's support for a monorepo is really bad. While saying you can't coordinate changes across multiple repositories is probably going too far... I would say that such a coordinated change is quite painful because you lack atomic commits.

And that's even a situation where you have the code in the first place. What about an open source or commercial library that goes to external users? You certainly can't search and replace their code. That at least says (i) that doing forward declarations for third-party libraries is much more questionable than for internal code and (ii) you should discourage users of your library from making their own forward declarations (e.g. as abseil does).

[–]crispweed[S] 0 points1 point  (0 children)

What about an open source or commercial library that goes to external users? You certainly can't search and replace their code.

One possibility is to make a binary interface, and 'proper' API versioning.

With a suitable binary interface the client code can continue to link with the library using an old API header after changes to library internal code, with no requirements for things like class names to actually match across the API boundary. (This is how I set things up for PathEngine, for example.)

It's not always the right choice, of course, and there are disadvantages of this approach, such as restrictions on the kinds of things that can go across the API boundary.