I noticed that in different C ++ projects, programmers use the #include directive in different ways

1) In the first case, include to the maximum prescribe in h-files, but no longer write in cpp-files. Those. you make the inclusion of one header file, and he already pulls all the inclusives in him.

2) In the second case, on the contrary, there are almost no include in h-files, but all these include will be written in cpp-files. However, if there are some common classes, then they can do this:

  • At the beginning of the header file put an empty class declaration: class myClass; . But this include will be in the cpp-file.

  • There is no external data type declaration in the header file at all. But it works if you connect header files with a description of classes in the cpp-file before connecting dependent header files, i.e. order is important

My questions are:

  • Is there a formal name for these source line layout styles?
  • How to do it right, but how can you not do it?
  • I want to explore this topic inside and out. Where to begin?
  • one
    The second approach is better because it does not create unnecessary dependencies. I try to write as much as possible in .cpp files, and put in in .h only when this can not be avoided. However, often used dependencies are better to render in a separate .h . If you are using MSVC, the total unchanged part is better to render to stdafx.h . - VladD
  • Thanks VladD, try this approach - Katilina

7 answers 7

In my experience, in most cases, this simple criterion works:

A "minimal" program with any of your inclusive files should be collected.

For example:

 // t.cpp #include "myincl1.h" int main () { return 0; } 

this is all text

 g++ t.cpp 

The compilation must pass.

And in "myincl1.h" it is desirable to include the minimum number of other .h files (both system and own).

And of course, do not forget to write

 #ifndef _MYINCL1_H #define _MYINCL1_H .... #endif // _MYINCL1_H 

at the beginning and end of their .h .

Actually, this is a requirement for a correctly written header file. Naturally, there should be no dependencies on the order in which the files are included.

Sometimes, to shorten the scribbling, it is convenient to write two or three "generalizing" .h files, which include most of the native and system .h needed for a particular program (used in many of its modules).

  • Very interesting evaluation criteria, avp, thank you for the answer! - Katilina

I want to explore this topic inside and out. Where to begin?

I have not seen any books about it

Is there a formal name for these source line layout styles?

I do not know

How to do it right, but how can you not do it?

Here are my arguments:

I think 2 situations should be distinguished:

  1. The header file contains some functions / classes (declaration + implementation) that can be used in different projects in various places. Such a file should include all the header files that he may need. According to the principle of "connected and forgotten", so that you do not need to connect any additional header files yourself.
  2. The header file (.h) contains the class / function description, and the file (.cpp) contains their implementation. In this case, or in the .h file, only those include will be included that contain objects that are present in the description of classes / functions (for example, std::vector ), or nothing is included, and everything is included in the .cpp file. The first method allows you not to dance with the order of inclusions in the .cpp file, the second does not make extra inclusions, but requires the correct order of connection.

IMHO connection rule is very simple, it is not difficult to remember and it is not difficult to follow:

 в файле MyClass.cpp #include <vector> (1) сначала подключаются файлы стандартной библиотеки #include <boost/smart_ptr.hpp> (2) затем подключатся файлы 3rd-party библиотек, объекты и функции которых Вы используете в своём коде #include <MyInclude/Helpers.h> (3) свои внутренние заголовочные файлы #include <MyClass.h> (3) свой заголовочный файл, реализацию для которого мы пишем 

A blank declaration myClass is a good solution (following the principle, the less inclusions and the more local they are, the better), but there is a pitfall (see why you need boost::checked_delete ). Usually empty ads are used for pointers, you need to ensure that these pointers are not deleted.

And do not forget about the existence of precompiled headers

  • Thank you for the detailed answer! - Katilina

It is difficult to unequivocally answer this question. Rather, it’s a matter of taste or even the scale of the task: if it’s some open source project, then for the convenience of others it’s advisable to put all the inclusions in the header file, if you solve the problem for yourself (do research, for example), then about this approach code breaks can not even think too much - one file is enough here, where everything, everything, everything will be located. But in general, a logical code break is a good habit.

  • four
    In my practice there is a negative example for the first approach. In one project, it is often necessary to make changes to the base header files, since there are a lot of different functions, including inline, and at the same time all the necessary include. After that, you have to rebuild the project entirely, because almost all h and cpp files include these basic headers. Apparently, it's time to divide these files into a bunch of smaller ones :) - Katilina

Well, in the case of "comprehensive" header-files - there may be a cyclic dependency. And it is quite likely if you do not adhere to any “style” clearly, then the development team may well achieve this effect. As a result, the "strongly connected" h-ku cannot be used in a dependent project, where even without it there are enough advertisements.

Therefore, my option is ideally to write like this in h-files to maintain order:

 #ifndef SOME_NEED_H #error Пожалуйста подключите SOME_NEED_H #endif 

So the riddles are fine. Well, if laziness - then really pch (carefully compiled) to help. I am for minimalism in h-kah and for some_header_tmpl.h (where everything is needed) for cpp.

    A "minimal" program with any of your inclusive files should be collected.

    It seems to me alone that this is a very strange criterion? I would like to warn the author of the question against such interesting criteria.

    In a good way, the header should be as small as #include. This is done, as a rule, in order to compile a project quickly [and only then, to build normal libraries, without unnecessary and useless dependencies].

    • I understood this so that one header file should not depend on another, and not give an error if you forgot to connect something other than it. For example, when mysql.h is connected, it is necessary to connect windows.h before it, otherwise there will be an error during compilation. #include <windows.h> #include <mysql.h> Ie criterion from the avp (author of the board) header file mysql.h does not pass. - Katilina
    • one
      IMHO the criterion is absolutely true, the compilation time (and the developer's nerves) otherwise is more than spent on knowing which include to plug in and in what order. To increase the build of projects came up with precompiled headers and #pragma once . - fogbit 4:05
    • 2
      @Katilina: In my opinion, this is a bug on the mysql.h side: the header should include everything that it needs. - VladD
    • one
      @ikonnov: I don’t think this criterion is strange. If header A needs a header B , this B must be included anyway, either from A or from all those who use A (otherwise it will not compile). Inclusion in A practically does not increase compile time, if you correctly use include guards. - VladD
    • 2
      why then forward declaration? - ikonnov

    If you want to have as few unpleasant surprises as possible with your header files, the module should include all the necessary header files for it. Do not rely on the one .h to include the other.

    If you are going to work with one compiler all your life, and one version, you can not see the difference. But as a rule, this does not work. And a well-written C ++ program should imply that it will be compiled by different compilers, and sometimes on different operating systems. And if you are accustomed to "minimalism", which some people advise here, you will find that in some conditions your program is going, in others it is not, and it literally depends on the phase of the moon. The fact is that in C ++ (in contrast to, say, Java, Oberon and even Object Pascal), there are no modularity language tools. There is only a separate compilation. And to ensure modularity, one has to use crutches - #include, namespace, etc. And their incorrect use is fraught with serious errors.

    In order to avoid cyclic dependencies, and also to prevent a module with a large number of included header files from being collected for too long, there are compilation guards (include guards, include guards) - see the Wikipedia article . And in a well-written header file, their use is mandatory! Unfortunately, some supporters of "minimalism" here, I am afraid, are unaware of their presence.

    Advice to collect links to frequently used headings into one "meta header" is also usually not very good. You change one file, and a dozen will be reassembled, with most of those that do not need it at all. Precompiling for large projects can be a good solution, but remember that in different compilers it may work differently.

    Maybe I wrote a lot of letters, but my advice is the advice of a person who has been writing portable programs for different operating systems for many years. And I had the opportunity to make sure that the cheap savings in assembly time sometimes turn into hard problems. Good luck in writing literate C ++ programs.

    • @DarkHobbit, frankly, I did not understand how "minimalism" is related to the problems of modularity. IMHO is just the opposite. Regarding include guards, you are absolutely right, and I also noted this in my reply: And of course, do not forget to write #ifndef _MYINCL1_H .... And for the "meta headline", of course, d. the rule is to change it as rarely as possible, you need to change the files that it connects, well, of course, use makedepend . - By the way, what is the essence of your advice? - avp
    • @avp: The problem with minimalism is this: if one compiler has header <map> # include header header <vector> , but the other doesn't, then this code: #include <map> map <int, vector <int >> m; will work on the first compiler, and will not be transferred to the second. That is, “minimal” (and here, as it were, we do not include the “extra” <vector> , as it seems to work without it) interferes with portability. - VladD
    • @VladD, this is generally a portability issue. Usually solved by conditional compilation directives (\ #ifdef ..., etc., etc.) A bunch (just huge) of examples in / usr / include -) (and almost all meet the "minimality" criterion). Actually, maybe the shortest and best advice for a vehicle would be - read / usr / include - avp
    • 2
      @avp: But if I immediately wrote #include <map> #include <vector>, then the problem would not have arisen anywhere. - VladD

    I know that the topic has long been outdated, but maybe someone will need it. The second case, when we select the minimum number of header files in .h, is good at reducing the compile time of the project if it is large enough. For example, this approach is actively used in Qt.