Skip to main content
gamedev.net
🔒 Locked

C++ Interfaces: When is it overkill?

Started by Nairou 15 replies 3.4k views
Original Post
Nairou
Nairou
I'm currently trying to reduce file dependency in my project. As it is right now, each class uses data from several other classes (no cyclic dependencies, thankfully), mostly in private data. However, since the full class definition is in the header file, including all private data, this means that the header file has to include all of the header files for the other classes it uses. In the end, making a single change to one of these header files, or one of the files using them, results in many more files having to be recompiled. I have been looking into using interfaces to eliminate this problem, where other classes would refer to the class through it's interface, hiding all private data and therefore greatly reducing the number of header files included everywhere. However, I know there is a cost involved with interfaces and all the inheritance, so I'm wondering if duplicating all of my classes (or at least all of the big widely used ones) into interface/class combos would really be such a good idea. Is it as bad as it sounds? Is there maybe a better alternative that people are using?
Sneftel
Sneftel
Quote:
Original post by Nairou
However, since the full class definition is in the header file, including all private data, this means that the header file has to include all of the header files for the other classes it uses. In the end, making a single change to one of these header files, or one of the files using them, results in many more files having to be recompiled.

Not necessarily. If the data members are pointers or references, then you only need to forward-declare the class, not include its headers. Anyways, if you want the compilation boost of interfaces without the OOP baggage, google for the "pimpl idiom". It's ugly and it's verbose, but these are the straits to which C++ drives us.
Verg
Verg
If you're programming in an OO language, designing to an interface is a good idea-- period.

I'm unsure what you mean by "private data", as that means (usually) that it is inaccessable by any other class, including derived classes... unless you declare the derived class a "friend".

You can always provide protected accessors and make them virtual... so that derived classes can access the private data.

As far as the "cost involved" for using virtual functions... generally speaking, a reasonably flat heirarchy is a good "cost" to pay if you're talking about using polymorphism.
riku
riku
C++ compile times have a tendency to increase a lot as a project gets larger. Sometimes long compile times start to reduce productivity and be very irritating. One possible solution is to get a faster computer, a better compiler or use a distributed environment.

The other option is to refactor your code with so-called "compiler firewalls". Such methods are the Pimpl idiom and the D-pointer (take a look at Wikipedia). The purpose of them is to get rid of complex dependencies between header files (and .cpp files too). They are based on the fact that you can make a pointer to an object with using a forward declaration only, rather than to creating an on the automatic store which requires you to have the entire class declaration (and thus a header file dependency).

Adapting compiler firewalls require you to do some refactoring of your existing code, add an extra pointer and freestore allocation (and cleanup). You can use std::auto_ptr or boost::shared_ptr to take care of cleanup.

-Riku
random_thinker
random_thinker
There is so much in C++ that is just mechanical and time wasting. Take the pImpl idiom. I use it a lot (its a great technique), but it just needlessly uses up time, because it is largely just a mechanical process.

It should be possible to take almost any class, press a button, and produces a pImpl idiom class. I mean, all you are doing is giving the pImpl class a name, and pointing to the same members.

Hey, maybe this can be done with regex?

Or maybe there are already systems out there that do this?
--random_thinkerAs Albert Einstein said: 'Imagination is more important than knowledge'. Of course, he also said: 'If I had only known, I would have been a locksmith'.
riku
riku
Quote:
Original post by random_thinker
There is so much in C++ that is just mechanical and time wasting.


Actually I used to think this way too. I spent a lot of time writing destructors, copycons and op='s, clean up code, try-catch blocks, etc. However, if you write good C++ you don't have to write this stuff, except for the very low level stuff. And you don't usually have to write that, since there is a lot of stuff available in the standard library and the boost libraries.

The pimpl idiom is not a very typical example of this, but it's true that it needs some mechanical work and the only advantage decreased compile times (unless you're writing a wrapper for something and creating a new interface for something old).

I don't really think that compiler firewalls are worth the effort in most cases. I usually don't have a problem with compile times and I do not make a lot of modifications to pieces of my projects that are included everywhere.

Quote:
Original post by random_thinker
Hey, maybe this can be done with regex?

Or maybe there are already systems out there that do this?


You could try. But parsing effectively parsing C++ involves a lot more than just regexes. A weakness of C++ is that it's not very easy to parse and even simple parsing requires a lot of effort.

You should remember that having separate header files and implementation files do have their advantages. For example, it's possible to recompile the files needed dependent on the changed files. I don't know how Java (or C# or any other language) handles this but at least I had some problems doing smart builds with Java (it was many years ago, tho). C++ compilers can also do a lot more optimiazations than say - a Java compiler. A Java compiler can't do a whole lot of inlining or call optimizations, for example.

-Riku
random_thinker
random_thinker
Riku-

Thx for the info; I suppose that one reason I complain about C++ is that I tend to do all my code by hand, rather than use an IDE. Even makefiles, etc. I just suits my needs.

I've come across a program called TextFilt (based upon Boost.Regex) and have a post here concerning the use of it and regex rules to filter template error messages during compilation. It seems to work very well for that, and I'm wondering if I should write the same thing to auto-produce pImpl class definitions and declarations. Under Linux, it should be possible to just 'cat | TextFilt pimpl_def.rs > ' and then again with the declarations and a new pimpl_decl.rs rule set.

I agree with you about taking a more 'streamlined' approach to writing; I now use the STL and boost extensively.

-random
--random_thinkerAs Albert Einstein said: 'Imagination is more important than knowledge'. Of course, he also said: 'If I had only known, I would have been a locksmith'.
d000hg
d000hg
OO techniques are good, in my opinion. Interfaces are a key concept in OO development. Wherever you can usefully extract an interface, I would recommend you do. I would hesitate to extract an interface just to speed up compilation though. My simple rule would be: If the class has derived classes or might do one day, it could be an interface.
You can't really do any harm having loads of interfaces, performance-wise it is not a worry. The time you save on compilation you could spend optimising some part of your code which needs it and get a net performance improvement, if that puts it into perspective?
random_thinker
random_thinker
I've always looked at interfaces as a way to hide the working code in a library from the end user. Proper interfaces allow distribution of a library with a simple header file, so that anyone can use it, without looking under the hood.
--random_thinkerAs Albert Einstein said: 'Imagination is more important than knowledge'. Of course, he also said: 'If I had only known, I would have been a locksmith'.
Nairou
Nairou
Thanks for all of the very informative replies! I too have tended to view interfaces as APIs, a way to expose classes to the public without revealing library internals.

In my current case, I'm looking for methods that can be extensively used internal to the project, as various classes interact with each other. I have used the "pimpl" method a couple times (so far only to keep platform-specific data out of public class definitions), but it looks like that may be the way to go for the rest of my classes as well.

Both methods still feel a little clunky, but for my needs pimpl seems to be the lesser of the two evils.
Aardvajk
Aardvajk
Not sure if anyone will agree or disagree with this, but I personally have a gut feeling that using a pattern like pimpl or a language feature like a forward declaration purely because it makes the program compile faster, rather than because it is a logical part of a good design is a bad idea.

I understand what you mean about alterations to private data resulting in cascading recompilation of other units that shouldn't really care about the private data, but of course that's C++'s antiquated module system for you.

To me, if you have to trade off between compile time and clear design, I'll take a few seconds longer to compile any day. Especially since I'll probably have to compile less times.

Just because something is OO does not mean it is necessarily the best design for every situation. Using pimpl, for example, purely to speed up compilation time has the knock-on effect of additional and unnecessary pointer indirection at runtime in the final executable, which while probably largely irrelevant on modern PCs, still feels like an icky trade off to me.
the_edd
the_edd
Quote:
Original post by EasilyConfused
Not sure if anyone will agree or disagree with this, but I personally have a gut feeling that using a pattern like pimpl or a language feature like a forward declaration purely because it makes the program compile faster, rather than because it is a logical part of a good design is a bad idea.

[ 8< --- ]

To me, if you have to trade off between compile time and clear design, I'll take a few seconds longer to compile any day. Especially since I'll probably have to compile less times.


I'd argue that the really isn't any clarity issue. In fact, it can make things clearer and easier in some cases.

// In your header file...class gizmo{    public:        // ...    private:        struct impl;        impl *pimpl_; // or more likely a smart pointer};// in your source file (.cpp, or whatever)...struct gizmo::impl{    hefty the;    hefty stuff;    hefty originally;    hefty in;    hefty the;    hefty header;};


When anyone with a little experience sees the word "pimpl" in the header, they're going to know what's going on immediately. Furthermore, when writing gizmo's implementation in the .cpp you don't have to keep switching to and from the header in your editor to see what the variable names are. They'll be at the top of your file, or in your "code browser/tree" panel in your IDE.

Granted, there's an extra level of indirection when accessing the members, but I wouldn't have said that this is any less clear.

Quote:
Just because something is OO does not mean it is necessarily the best design for every situation. Using pimpl, for example, purely to speed up compilation time has the knock-on effect of additional and unnecessary pointer indirection at runtime in the final executable, which while probably largely irrelevant on modern PCs, still feels like an icky trade off to me.


It is icky. But C++ is an icky language some of the time. I don't think that anyone's suggested that it be used in every situation, either. And I wouldn't have said it was at all 'OO'. The emergence of the idiom is merely an artifact of the particular language's design.

But when you have a project with a several hundred thousand LOC, you don't want to be waiting around for 10 minutes for the thing to compile and link just because you changed a couple of lines in a header. You lose your train of thought by the time your change is ready to test.

This kind of pattern even found its way in to the C++ standard many years ago: .

Edd
random_thinker
random_thinker
One of the things to remember is that good software engineering should minimise coupling: this implies that, if a relationship can be expressed in more than one way, use the weakest relationship that's practical. Given that inheritance is just about the strongest relationship we can express in C++, perhaps second only to friendship, it's only really appropriate when there is no equivalent weaker alternative. If you can express a class relationship using composition alone, you should prefer that. The pimpl-idiom comes into play here also, as the concept is very close to composition. By composition, I mean embedding a member variable of a type within another type. Accordingly, you can hold and use the object in ways that allow you control over the strength of the coupling. With a pimpl-idiom, you can accomplish the same, and insulate yourself and your users from changes in underlying code.

In the early days, I used a lot of inheritance (I don't think that I've ever used friends, even once), then moved to composition and pimpl-idioms, and functional decomposition wrapped in namespaces, depending upon needs. All the pieces are easily updated and accordingly simplified/improved with time, but the interfaces are largely unaffected.

-random
--random_thinkerAs Albert Einstein said: 'Imagination is more important than knowledge'. Of course, he also said: 'If I had only known, I would have been a locksmith'.
d000hg
d000hg
When you have a project with several hundred thousand LOC, it's pretty rare you need to change one of the core headers used by everything. You normally just compile modified/new files and that's very fast. Especially when you actually put your code in .cpp files not in the class header. Then you can change even the core classes without modifying the header in many cases.

As for pimpl, I worked as a professional C++ developer for a number of years and never heard of it. It sounds like a bad idea.
the_edd
the_edd
Quote:
Original post by d000hg
When you have a project with several hundred thousand LOC, it's pretty rare you need to change one of the core headers used by everything. You normally just compile modified/new files and that's very fast.

Usually yes. But if you have a huge project that relies on, boost, say and you upgrade to the latest boost release, if one of your widely-used headers depends on a boost header, you could be waiting for hours for your code base to recompile. These things aren't always under your control.

Quote:
As for pimpl, I worked as a professional C++ developer for a number of years and never heard of it. It sounds like a bad idea.

Argumentum ad verecundiam, I see! ;) But seriously, I'm genuinely interested in hearing what's so bad about it.

IMO, the text-based inclusion mechanism of C++ is the thing that is the bad idea. The pimpl idiom is merely one particular workaround. It's really nothing more than a glorified forward declaration.

Edd

[Edited by - the_edd on August 21, 2007 4:54:42 AM]
quasar3d
quasar3d
Quote:
Original post by d000hg
When you have a project with several hundred thousand LOC, it's pretty rare you need to change one of the core headers used by everything. You normally just compile modified/new files and that's very fast. Especially when you actually put your code in .cpp files not in the class header. Then you can change even the core classes without modifying the header in many cases.

As for pimpl, I worked as a professional C++ developer for a number of years and never heard of it. It sounds like a bad idea.


well... that would be the ideal situation, but in reality I've never seen it actually being the case. In many projects compile times are really quite a big workflow problem, and then something like pimpl, which may look a little bit like a hack, is definitely worth it.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.