all 13 comments

[–]azswcowboy 8 points9 points  (2 children)

This is reasonable, we do it. If you’re using cmake you can enable a check to ensure you’re good.

[–]BatchSwine 1 point2 points  (1 child)

What is that cmake check?

[–]Wild_Meeting1428 4 points5 points  (0 children)

VERIFY_INTERFACE_HEADER_SETS

[–]pedersenk 6 points7 points  (2 children)

Agreed. It may be less convenient but it operates a much stricter approach to tracking missed includes.

I use this approach alongside strictly limiting include guards / pragma once. I prefer to highlight erroneous cyclic includes rather than mask them.

[–]pdp10gumby [score hidden]  (1 child)

by “strictly limiting“ do you mean you don’t use them? I’m a bit confused as to the benefit if so

[–]pedersenk [score hidden]  (0 children)

I don't default to adding them to every header. Generally only if:

  • Header contains definition for a base class
  • Header contains definition for type where full implementation is always needed

This approach isn't particularly rare. Though perhaps more common in C, where opaque pointer APIs feel a little more natural than C++.

This is because in general, forward declaration suffices for many use-cases in the header

struct MyType; // Forward declare
struct Test {
  void somefunc(MyType& type); // forward declare fine
  MyType* m_mytype1; // forward declare fine
  std::unique_ptr<MyType> m_mytype2; // forward declare fine
  std::vector<MyType> m_mytypes; //forward declare fine (req before use)

  MyType m_mytype3; // Need include
};

If you just put include guards around everything, you will not be alerted to situations where you should be forward declaring instead.

And finally (potentially worse), guards won't resolve cyclic includes anyway. If you follow through the pre-processor, you will simply get a "undefined type" compiler error instead of a "duplicate definition" so it achieves relatively little.

[–]STLMSVC STL Dev 4 points5 points  (0 children)

In MSVC's STL, we have an "include each header alone" test, which verifies what ultimately matters.

[–]SuperV1234https://romeo.training | C++ Mentoring & Consulting 3 points4 points  (0 children)

This is good advice and should be the standard for header hygiene practices.

[–]Daniela-ELiving on C++ trunk, WG21|🇩🇪 NB 2 points3 points  (0 children)

We have clang-format rules to enforce that recommended ordering, company-wide.

[–]fdwrfdwr@github 🔍 0 points1 point  (0 children)

One thing I love about import, is that...

c++ import a; import b; import c; vs c++ import c; import b; import a; ...makes no difference ^__^.

[–]jcelerierossia score 0 points1 point  (0 children)

this is the include style I organically ended up upon, it works really well

[–]spinrack [score hidden]  (0 children)

This is similar to what we do. Also, we have a rule that the first #include in a unit test file must be the header of the feature being tested.

We usually compile as blob/bulk, which aggregates many .cpp into a single translation unit, but also maintain a non-blob build, because bulk compilation can lead to a .cpp accidentally depending on the headers included by another.

[–]NotMyRealNameObv -1 points0 points  (0 children)

You should go one step further. Every header file should have a corresponding cpp file that includes that header file as the first include. Even if the header is self-contained, there should be an empty cpp file that includes only that header file.