5

This question is not intended to be a repeat of "Why should I not include cpp files and instead use a header?" and is more of a question of practices.

To best frame this question let me explain. When writing a class it can grow quickly to over a few hundred lines if not more. For readability purposes I would like to break a class into individual files on a per method basis. To be clear I am not suggesting making the entire project into a set of includes for reasons mentioned in the post listed above, but rather break a class into components contained in their own files.

The following snippets of code is an illustration of what I mean

main.cpp

#include <iostream>
#include "helloClass.h"
using namespace std;

int main()
 {
         hello a;
         cout<<a.out();
         cin.get();//just to pause execution
 }

helloClass.h

#ifndef HELLOCLASS_H
#define HELLOCLASS_H
#include <string>
class hello
{
std::string  message;

public:
std::string out();
hello();
};

#endif

helloClassMain.cpp

#include "helloClass.h"     
using namespace std;        
hello::hello()              
{                           
   message = "Hello World!";   
};                          

#include "helloClassOut.cpp"

helloClassOut.cpp

string hello::out()
{                  
return message;    
}                  

This will compile fine and execute as expected. I find an added benefit when there happens to be an error because the compiler will not only tell you what line but also the file that the error is in. For example I compiled it with a undeclared variable

$ c++ main.cpp helloClassMain.cpp -o hello
In file included from helloClassMain.cpp:8:0:
helloClassOut.cpp: In member function ‘std::string hello::out()’:
helloClassOut.cpp:3:1: error: ‘fail’ was not declared in this scope
fail="test";

I find this helpful to say the least and it allows me to think of the file helloClassMain.cpp as the entry point for the hello class and all of it's methods and attributes.

I understand that this is in the end the same thing as having the class written out in the same file because of how a compiler works. It is just a matter of being able to easier read, troubleshoot, etc.

Finally the questions.

  1. Is this bad practice?
  2. Would it make hell for those who are collaborating with me on a project?
  3. Is there a core concept that I am missing here? I am sure I am not the first to consider this solution.
  4. Have I asked a question more about preference and not so much best practices?
  5. Finally is there a name for what I am describing other than bad?
Community
  • 1
  • 1
  • 4
    Have you considered that your classes simply do too much work, i.e. that they violate the single-responsibility principle? – Christian Hackl Feb 21 '15 at 18:09
  • *Yes!* to #2 if *I* was a collaborator. – Biffen Feb 21 '15 at 18:11
  • @Christian are you saying standards all stay under hundreds of lines for a class? I would suggest finding an IDE you like, tends to help a lot with large classes. – Christian Sarofeen Feb 21 '15 at 18:16
  • 1
    I feel if a class is so large/complex you need to break it down like this then it is probably worth compiling the cpp files separately rather than `#include` them. – Galik Feb 21 '15 at 18:23
  • 1
    See [this c++ style guide](http://www.literateprogramming.com/ellemtel.pdf), it mentions some situations to put functions in a separate file. So if you can make it clear why you want to do that, and think about it well, you can do that. But it seems that most people want to put functions together in a file. Probably because it has too many disadvantages to use separate files. – wimh Feb 21 '15 at 18:43
  • Thank you all for the quick responses. This has been more than helpful. – superxkooda Feb 21 '15 at 19:05

2 Answers2

4
  1. Is this bad practice?

Usually, yes. The only time I can think where this might be desirable is if you have some methods that are implemented differently on different systems (i.e. Windows and Linux). If there's a system-specific method, you might do something like this. But otherwise it's frowned upon.

  1. Would it make hell for those who are collaborating with me on a project?

Yes, because:

  • Intellisense doesn't work with your rogue files (since they don't #include the definitions they need).
  • You're creating .cpp files that are #included instead of compiled. That's misleading.
  • #includeing .cpp files into another .cpp file is a great way to get namespace clashes. Maybe one .cpp file has a global, static helper function that conflicts with another .cpp's global static helper. Maybe you using namespace std; in one file messes up another file.
  • If you were to compile your .cpp files instead of #include them, you could take advantage of parallel compilation. Using #include is going to be much slower, because if you change one method just a little, then all of them have to be reprocessed and compiled. If instead you compiled every .cpp separately (which is the normal thing to do), then changing one .cpp file means only that file needs to be reprocessed and recompiled.
  1. Is there a core concept that I am missing here? I am sure I am not the first to consider this solution.

Without a more concrete example, you might be missing out on the single responsibility principle. Your class might be too big and doing too much.

Also, classes represent objects with state; spreading that state across multiple files makes it harder to conceptually understand that state as a whole (if it's all in the same file, it helps me to see and understand the whole object's state). My bet is that you'll have more bugs because the object's state isn't as consistent as you think it is.

  1. Have I asked a question more about preference and not so much best practices?

It's a bit of both. Now we're getting meta, though.

  1. Finally is there a name for what I am describing other than bad?

Perhaps there's a technical name, but I'd lean towards poisoned chalice.

Cornstalks
  • 37,137
  • 18
  • 79
  • 144
  • 1
    When writing library code, "best practice" is to put each function in a separate source file. – James Kanze Feb 21 '15 at 18:31
  • 2
    @JamesKanze: To be quite honest, I've never seen any project do that. I'm skeptical it's an accepted "best practice." – Cornstalks Feb 21 '15 at 18:33
  • Maybe if "best practice" was on a 40-year-long bender and forgot who and when it was. – Puppy Feb 21 '15 at 18:35
  • your response to #1 don't most people use macros to handle multiform? – superxkooda Feb 21 '15 at 19:25
  • @superxkooda: perhaps, but not always. It gets messy sprinkling `#if`, `#elif`, etc. throughout the code. Sometimes it's easier to move the code (especially if it's a large chunk) into a new file, and then only compile that file on the right platform. No macro mess required. But for little things, yes, the preprocessor is typically used. – Cornstalks Feb 21 '15 at 20:10
  • @Cornstalks I guess you've never worked on quality libraries. In an application, of course... if the function isn't used, you don't write it. But this has been standard practice for all of the libraries I've seen. – James Kanze Feb 21 '15 at 23:15
  • @superxkooda If the functions involved are simple enough to be implemented with macros, they might. But `#ifdef` and company are generally avoided except for include guards. – James Kanze Feb 21 '15 at 23:17
  • 1
    @JamesKanze: I know the historical reason - compilers failed to put functions in their own linker segments, so pulling in one function pulled in everything else from that file - but is that still a problem today? Yes, I know that GCC hasn't yet made `-ffunction-segments` the default, but isn't that the new best practice for such libraries? – MSalters Feb 21 '15 at 23:33
  • @MSalters The best practice is still to write to the lowest level compiler/linker you might have to deal with. And it's a mixture of compiler and linker issues; g++ can't make it the default, because it doesn't know whether the linker will support it. And of course, if your generating exception handling correctly, you have the exception handling code for the function in a separate segment already, which complicates the issue somewhat. – James Kanze Feb 22 '15 at 16:22
1

You don't actually need to #include all .cpp files in a single one.

You can for example implement each method in a different .cpp file, then compile them independently. You will get several .o files, each exporting the method it implements. The linker will do the job of tying them together all the same.

So :

  1. Splitting implementations is not bad practice if done well. Don't #include them however.

  2. Not if you document it well. For example, group methods together in your .h and put a comment indicating in which file they're implemented.

  3. The linking process, which doesn't care where a symbol comes from as long as it's exported by one object file.

  4. I don't think so, even if it could be aimed closer to the problem (how do I split an implementation ?).

  5. Not that I'm aware of.

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • 1
    Traditionally, the "best practice" was in fact to put each function in a separate source file. The resolution of the linker was an object file, and by putting each function in a separate source, and all of the generated object files in a library, your executable only included the functions it actually used. – James Kanze Feb 21 '15 at 18:30
  • @JamesKanze yes, and that's sleek. But it's also a nightmare to do so with current IDEs. Or is there somewhere an IDE that does it ? – Quentin Feb 21 '15 at 18:33
  • @JamesKanze: don't modern linkers strip out unused functions? – Cornstalks Feb 21 '15 at 18:39
  • @Cornstalks that's a separate and lengthy process IIRC. `strip` does it. – Quentin Feb 21 '15 at 18:51
  • @Quentin: does `strip` really do it? I though it just removed symbol names and things like that, not actual runnable instructions. It seems like that could wreck havoc with jumping to functions at runtime... – Cornstalks Feb 21 '15 at 19:08
  • @Cornstalks you're right, it's just symbols. Well I don't know then ! – Quentin Feb 21 '15 at 19:12
  • @Cornstalks like this: http://stackoverflow.com/a/6770305/880984 ? It would appear that it doesn't automatically happen. – Tim Seguine Feb 21 '15 at 22:38
  • @Cornstalks Most don't, and given the way compilers generate (or should generate) optimized code and exceptions, it's not really practical. – James Kanze Feb 21 '15 at 23:14