Separating C++ Class Code into Multiple Files, what are the rules?

39,394

Solution 1

Your three points sound about right. That's the standard way to do things (although I've not seen .tpp extension before, usually it's .inl), although personally I just put inline functions at the bottom of header files rather than in a separate file.

Here is how I arrange my files. I omit the forward declare file for simple classes.

myclass-fwd.h

#pragma once

namespace NS
{
class MyClass;
}

myclass.h

#pragma once
#include "headers-needed-by-header"
#include "myclass-fwd.h"

namespace NS
{
class MyClass
{
    ..
};
}

myclass.cpp

#include "headers-needed-by-source"
#include "myclass.h"

namespace
    {
    void LocalFunc();
}

NS::MyClass::...

Replace pragma with header guards according to preference..

The reason for this approach is to reduce header dependencies, which slow down compile times in large projects. If you didn't know, you can forward declare a class to use as a pointer or reference. The full declaration is only needed when you construct, create or use members of the class.

This means another class which uses the class (takes parameters by pointer/reference) only has to include the fwd header in its own header. The full header is then included in the second class's source file. This greatly reduces the amount of unneeded rubbish you get when pulling in a big header, which pulls in another big header, which pulls in another...

The next tip is the unnamed namespace (sometimes called anonymous namespace). This can only appear in a source file and it is like a hidden namespace only visible to that file. You can place local functions, classes etc here which are only used by the the source file. This prevents name clashes if you create something with the same name in two different files. (Two local function F for example, may give linker errors).

Solution 2

The main reason to separate interface from implementation is so that you don't have to recompile all of your code when something in the implementation changes; you only have to recompile the source files that changed.

As for "Declare the class (template or otherwise)", a template is not a class. A template is a pattern for creating classes. More important, though, you define a class or a template in a header. The class definition includes declarations of its member functions, and non-inine member functions are defined in one or more source files. Inline member functions and all template functions should be defined in the header, by whatever combination of direct definitions and #include directives you prefer.

Share:
39,394
FreelanceConsultant
Author by

FreelanceConsultant

No longer available for hire for contract work Expertise include: Research (Any) Data Analysis, Signal Processing Mathematics C, C++ and Python Multi-thread, parallel and networked CUDA, OpenCL

Updated on April 27, 2020

Comments

  • FreelanceConsultant
    FreelanceConsultant about 4 years

    Thinking Time - Why do you want to split your file anyway?

    As the title suggests, the end problem I have is multiple definition linker errors. I have actually fixed the problem, but I haven't fixed the problem in the correct way. Before starting I want to discuss the reasons for splitting a class file into multiple files. I have tried to put all the possible scenarios here - if I missed any, please remind me and I can make changes. Hopefully the following are correct:

    Reason 1 To save space:

    You have a file containing the declaration of a class with all class members. You place #include guards around this file (or #pragma once) to ensure no conflicts arise if you #include the file in two different header files which are then included in a source file. You compile a separate source file with the implementation of any methods declared in this class, as it offloads many lines of code from your source file, which cleans things up a bit and introduces some order to your program.

    Example: As you can see, the below example could be improved by splitting the implementation of the class methods into a different file. (A .cpp file)

    // my_class.hpp
    #pragma once
    
    class my_class
    {
    public:
        void my_function()
        {
            // LOTS OF CODE
            // CONFUSING TO DEBUG
            // LOTS OF CODE
            // DISORGANIZED AND DISTRACTING
            // LOTS OF CODE
            // LOOKS HORRIBLE
            // LOTS OF CODE
            // VERY MESSY
            // LOTS OF CODE
        }
    
        // MANY OTHER METHODS
        // MEANS VERY LARGE FILE WITH LOTS OF LINES OF CODE
    }
    

    Reason 2 To prevent multiple definition linker errors:

    Perhaps this is the main reason why you would split implementation from declaration. In the above example, you could move the method body to outside the class. This would make it look much cleaner and structured. However, according to this question, the above example has implicit inline specifiers. Moving the implementation from within the class to outside the class, as in the example below, will cause you linker errors, and so you would either inline everything, or move the function definitions to a .cpp file.

    Example: _The example below will cause "multiple definition linker errors" if you do not move the function definition to a .cpp file or specify the function as inline.

    // my_class.hpp
    void my_class::my_function()
    {
        // ERROR! MULTIPLE DEFINITION OF my_class::my_function
        // This error only occurs if you #include the file containing this code
        // in two or more separate source (compiled, .cpp) files.
    }
    

    To fix the problem:

    //my_class.cpp
    void my_class::my_function()
    {
        // Now in a .cpp file, so no multiple definition error
    }
    

    Or:

    // my_class.hpp
    inline void my_class::my_function()
    {
        // Specified function as inline, so okay - note: back in header file!
        // The very first example has an implicit `inline` specifier
    }
    

    Reason 3 You want to save space, again, but this time you are working with a template class:

    If we are working with template classes, then we cannot move the implementation to a source file (.cpp file). That's not currently allowed by (I assume) either the standard or by current compilers. Unlike the first example of Reason 2, above, we are allowed to place the implementation in the header file. According to this question the reason is that template class methods also have implied inline specifiers. Is that correct? (It seems to make sense.) But nobody seemed to know on the question I have just referenced!

    So, are the two examples below identical?

    // some_header_file.hpp
    #pragma once
    
    // template class declaration goes here
    class some_class
    {
        // Some code
    };
    
    // Example 1: NO INLINE SPECIFIER
    template<typename T>
    void some_class::class_method()
    {
        // Some code
    }
    
    // Example 2: INLINE specifier used
    template<typename T>
    inline void some_class::class_method()
    {
        // Some code
    }
    

    If you have a template class header file, which is becoming huge due to all the functions you have, then I believe you are allowed to move the function definitions to another header file (usually a .tpp file?) and then #include file.tpp at the end of your header file containing the class declaration. You must NOT include this file anywhere else, however, hence the .tpp rather than .hpp.

    I assume you could also do this with the inline methods of a regular class? Is that allowed also?

    Question Time

    So I have made some statements above, most of which relate to the structuring of source files. I think everything I said was correct, because I did some basic research and "found out some stuff", but this is a question and so I don't know for sure.

    What this boils down to, is how you would organize code within files. I think I have figured out a structure which will always work.

    Here is what I have come up with. (This is my class code file organization/structure standard, if you like. Don't know if it will be very useful yet, that's the point of asking.)

    • 1: Declare the class (template or otherwise) in a .hpp file, including all methods, friend functions and data.
    • 2: At the bottom of the .hpp file, #include a .tpp file containing the implementation of any inline methods. Create the .tpp file and ensure all methods are specified to be inline.
    • 3: All other members (non-inline functions, friend functions and static data) should be defined in a .cpp file, which #includes the .hpp file at the top to prevent errors like "class ABC has not been declared". Since everything in this file will have external linkage, the program will link correctly.

    Do standards like this exist in industry? Will the standard I came up with work in all cases?

    • Neil Kirk
      Neil Kirk over 10 years
      btw I skimmed your question as it was too long. About the inline template function. The inline keyword is not required (you are still allowed to put them in header files) and doesn't have any guaranteed effect. Using the keyword may make the compiler more likely to inline the function, but nothing is certain. These template functions are not implicitly inline functions. The answer to that question is wrong, as discussed in the answer's comments.
    • FreelanceConsultant
      FreelanceConsultant over 10 years
      @NeilKirk Ah thanks, there certainty seemed to be a lot of disagreement about it.
    • Neil Kirk
      Neil Kirk over 10 years
      Just ignore putting inline there and the issue will never bother you :)
    • Chemistpp
      Chemistpp over 10 years
      I think I answered the wrong question, ignore my answer.
    • James Kanze
      James Kanze over 10 years
      @NeilKirk The inline keyword is required if the function is defined outside of the class definition in a header (and it's generally frowned on to put function definitions inside a class definition).
    • Neil Kirk
      Neil Kirk over 10 years
      @JamesKanze Not for templates.. sorry if I didn't make this clear enough in my comments.
    • FreelanceConsultant
      FreelanceConsultant over 10 years
      @NeilKirk inline is required for a regular class, if the implementation is not placed in a .cpp file.
  • FreelanceConsultant
    FreelanceConsultant over 10 years
    Ah yeah, good tips about anonymous namespace and forward declaration header file. I can see a lot of times where the forward declaration particularly would be useful. Thanks!
  • quamrana
    quamrana over 10 years
    @NeilKirk: Why do you include myclass-fwd.h in myclass.h? I would have thought that was redundant.
  • Neil Kirk
    Neil Kirk over 10 years
    @quamrana No reason, just to be consistent.
  • FreelanceConsultant
    FreelanceConsultant over 10 years
    @quamrana I quite like that as it reminds you that somewhere you have a file which probably isn't open in your workspace containing just a class pre-declaration.
  • FreelanceConsultant
    FreelanceConsultant over 10 years
    Header only as in: All methods declared and defined within the class braces ({ and })? I get your point about preventing linker errors, but any member which is more complex than one line of code, for example, more complex than just { return m_time; }, produces hideous code.
  • FreelanceConsultant
    FreelanceConsultant over 10 years
    "As for "Declare the class (template or otherwise)", a template is not a class. A template is a pattern for creating classes." - yeah I know
  • David Hammen
    David Hammen over 10 years
    Header only, as in all methods are defined inside some header file. Whether that's inside the class, as an out of line definition outside the class but still in the same header file, or some other file that's included by that first header is a different question. The key point: There is no source file that needs to be compiled separately and linked in. A lot of Boost, for example, is a header-only implementation.
  • David Hammen
    David Hammen over 10 years
    As far as something more complex than { return something; } not belong inside the class definition: That's your opinion. Other people have other opinions. I personally don't mind seeing a short (but not one-liner) inside the class definition. My personal rule: If I put a function definition inside the class if I think doing so will help others to understand that class, That's my opinion. Be very wary of putting personal preference issues inside a coding standard. It will make people ignore your standards.
  • FreelanceConsultant
    FreelanceConsultant over 10 years
    I think I have misunderstood you in some way... are you are suggesting placing the class member function definitions inside the same header file as the class, but outside of the class... as in you had to type <return type> [class_name]::[function_name]() { // Code here } ?
  • David Hammen
    David Hammen over 10 years
    You completely misunderstood me. There are no source files to compile in a header-only implementation. Everything needed, including function definitions, is somewhere in some header. Your .tpp file is a header file, just with a non-standard suffix. It's still #included rather than compiled. With regard to where one should put those member function that are defined in a header file (inside the class definition versus out-of-line but qualified with inline if needed to keep with the one definition rule), that's personal preference.
  • FreelanceConsultant
    FreelanceConsultant over 10 years
    Ah okay In that case I'll agree with you