Sign in to follow this  
F-Kop

Is the definition/declaration model all that important?

Recommended Posts

I've been searching for a while for a new language to use, mostly for Win32 programs. The more I research, the more I realize that C++, which I've been using for years, fits my needs more than any other language out there. At least of the ones I've seen, which is a lot. But I've been wondering..C++ is a very old language, and has undergone many changes for the better. But what exactly is the benefit of forward declarations? Because forward declarations are required in C++, we have to make header files. If you code in a structure similar to mine, you will need to make two new files each time you create a major class. If we make a change to the class, we have to make that change in two places, which we sometimes forget to do. It was save some time to only have to make the change in one place. That, of course, doesn't include references to the updated class or function. Another thing that I often see questions about is hiding private members of classes. If there is no header to read, then all members are hidden. The only downfall to this is that documentation becomes more of a necessity for libraries that don't include source code. So is there a positive side to headers/forward declarations that I am missing? [Edited by - F-Kop on January 31, 2008 11:39:57 PM]

Share this post


Link to post
Share on other sites
I actually like having the code split into the headers and implementation files. I find it neater, plus you can get a good overview of a class without having to rely on IDE-specific features.

Also, when designing a new class, I generally take my pen+paper notes and write the header file first. Then once the interface is defined I go and write the implementation.

Writing Java-style code with monolithic class files makes me feel dirty...

[EDIT]
From a technical viewpoint - having headers/forward-declarations etc, allows the compiler to be faster as it's got one less pass to take.
However, when care isn't taken to structure headers properly you can greatly increase compilation times by dragging in lots of code that isn't needed.

Share this post


Link to post
Share on other sites
Yeah, one less pass. (which is offset by anything templated and doesn't nearly make up for the comparatively slow syntax parsing caused by C++'s poor design)


And if you want a nice overview, use any vaguely modern IDE which supports code folds (VS, netbeans). There is really no good argument in favor of forward declarations that I am aware of.

Share this post


Link to post
Share on other sites
In my opinion, having to have headers separate from implementation and using forward declarations is just a consequence of using an aging one-pass compilation system. C#, Java, and tons of other languages are far smarter (and faster) when it comes to compiling. I expect that this one-pass compile system was good at the time to keep memory requirements low, but now memory usage during a compile is of less concern.

I like C++ a lot and it is the language I enjoy working in the most, but if there were something I would change about C++, the way building works would be a strong candidate.

Now as to why forward declarations are necessary, it allows you to have some limited cyclical dependencies between classes, for example:

class B;

class A
{
B *getB();
}

class B
{
A *getA();
}

I say limited because you couldn't for example implement getB() inline as { return new B(); } because B isn't defined yet. You COULD do this after B is defined (like in the h file at the end or in the cpp file that includes this h file).

Also, implementing code within the class (i.e. Java style) suggests to the compiler that your code is inlined, so if you did decide to use Java-style code structure, your compiled binary would be massive. I'm not sure if the compiler might help you out by ignoring you or not, but just be aware of this :)

Forward declarations, if they save you from including the Header.h that the object is defined in when you don't need it, would speed up compilation in the case that Header.h (or one of its dependencies) is changed. So if you're crafty in how you use forward declarations, you can reduce your compile times significantly depending on what you edited since the last compile.

(EDIT: fixed a misprint in the code sample, and BTW IMHO cyclical dependencies of classes can be avoided without forward declarations.. you can break the cycle by for example having B extend an interface and just let A use/return that interface.)

Share this post


Link to post
Share on other sites
Quote:
Original post by F-Kop
C++ is a very old language...

It's not that old. It was only standardized in 1997.

Quote:
But what exactly is the benefit of forward declarations?

None. It's a side-effect of a compilation model chosen due to the constraints of computational power in 1984 or so. Unfortunately, the attendant behaviors have been codified into the language, so compiler vendors can't provide the rational alternative - after all, one of the attendant behaviors is the use of header files!

Quote:
Another thing that I often see questions about is hiding private members of classes. If there is no header to read, then all members are hidden. The only downfall to this is that documentation becomes more of a necessity for libraries that don't include source code.

You can ship C++ headers that exclude private members. They just provide interface declarations, after all.

Share this post


Link to post
Share on other sites
Quote:
Original post by aclysma
Also, implementing code within the class (i.e. Java style) suggests to the compiler that your code is inlined, so if you did decide to use Java-style code structure, your compiled binary would be massive. I'm not sure if the compiler might help you out by ignoring you or not, but just be aware of this :)


Inlining in c++ is usually a request you make to the compiler and it is allowed to ignore you if it likes. I'm not sure that this applies to methods implemented in classes, but that is true when you use the keyword inline.

Share this post


Link to post
Share on other sites
Quote:
Original post by F-Kop
Because forward declarations are required in C++, we have to make header files.

Careful. A forward declaration is not what you're talking about here. You're talking about the separation of definition from declaration that's required to implement the separate compilation model used by the 1950's-era COBOL and FORTRAN compilers, and later adopted by the 1960's-era C compiler. What these languages had in common was that they produced code that ran on hardware. C++ is an extension of that legacy, and continues to use the same separate comilation model and continues to produce code that runs on hardware.

One of the niceties of the C-style separate compilation model is that a module written in any language can be linked to a module written in another language. One of the downfalls is that the linker can know nothing about the source code from which the separate object modules are generated. In engineering circles, losse coupling is considered desireable. The C-style link model is loosely coupled: the compiled language knows nothing about the real hardware, the hardware knows nothing about the compiled language, and most of the intermediate tools are not dependent on other tools in the chain.

Modern languages like C# and Java also have a separate compilation model, but they do not produce code that runs on hardware. Instead, they produce code interpreted by another program. That program might interpret the intermediate code immediately or it might compile it into native code and execute it. The produced code also contains a great deal of information about the original source code, so the compiler can also read the intermediate code to discover information it needs. The compiler and the runtime are intimately linked and very closely coupled to the virtual hardware. This makes it possible to eliminate the separate definition/declaration model required by C++ at the expense of making it difficult or impossible to interface with other languages or, for example, hardware.
Quote:
So is there a positive side to headers/forward declarations that I am missing?

I can't say there is an advantage to using the separate declaration/definition model used in C++. It sure can be a real pain. But it does enable the separate compilation model that's been used for many decades, which means you can use C++ to extend existing systems and leverage legacy code. The newer compilation models for the most part require reinventing the old wheels or doing complex dances to shoehorn legacy stuff in to the newer models, and when the next silver-bullet-flavour-of-the-week comes along, I suspect the good old FORTRAN link model will still be chugging away while the JVM and .NET are rusting away on a historic footnote.

--smw

Share this post


Link to post
Share on other sites
Quote:
Original post by Bregma
I can't say there is an advantage to using the separate declaration/definition model used in C++. It sure can be a real pain.


It allows for considerably better run-time performance through inlining and removal and you don't pay for what you don't use.

In Java or C#, interface and implementations are defined implicitly. But, since every method in every class may be called at any time - the compiler must keep every single class definition in its verbatim form. Hence, hundreds of thousands of one-liner classes, littering the VM.

Something like Boost would be unthinkable in those languages, since they would result in millions of classes, all alike.

C++'s headers are essentially IDLs, but the language isn't constrained to them in the way CORBA is for, for example.

The trade-off is that reflection or introspection are impossible, and the generated code is monolithic.

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
Quote:
Original post by Bregma
I can't say there is an advantage to using the separate declaration/definition model used in C++. It sure can be a real pain.

It allows for considerably better run-time performance through inlining and removal and you don't pay for what you don't use.

That doesn't require a separate declaration/definition model. Inlining and redundant and unused symbol removal can be done at link time without separate declaration and definition. With profile guided optimization, inlining can actually be done more optimally at link time.

Quote:
In Java or C#, interface and implementations are defined implicitly. But, since every method in every class may be called at any time - the compiler must keep every single class definition in its verbatim form. Hence, hundreds of thousands of one-liner classes, littering the VM.

That's not a consequence of the interface and implementation unification, but instead a consequence of the linkage and virtual machine models.

Quote:
The trade-off is that reflection or introspection are impossible, and the generated code is monolithic.

Reflection and introspection are possible with a single pass compilation model. The lack of reflection in C++ is a consequence of the type system and runtime type information design. C++ dialects have been produced in academic research environments that contain reflection without doing away with single pass compilation.

Share this post


Link to post
Share on other sites
Quote:
Original post by F-Kop
I've been searching for a while for a new language to use, mostly for Win32 programs. The more I research, the more I realize that C++, which I've been using for years, fits my needs more than any other language out there. At least of the ones I've seen, which is a lot.


I'm sorry, I have to bring this up. This doesn't have a THING to do with your question so why did you include it? You could have simply left all that out, asked your question, and been done. But you did say it and now it has me wondering your needs are that you think only C++ can fill them. So, why C++?

Share this post


Link to post
Share on other sites
One of the main reasons for the C++ compilation model is that Stroustrup wanted the early C++ compilers to work with linkers designed for C, since a linker is a non-trivial bit of software and being able to use C linkers (which already had well tested implementations on just about every platform) would make it much easier to port C++ to many different platforms. In fact the first C++ compiler generated C code and then used C compilers and linkers which meant it could easily be ported to any platform that had a C compiler and linker, which is pretty much the same thing as saying 'any platform'.

The Design and Evolution of C++ talks about this and helps explain many of the other idiosyncracies of C++ which can seem strange or arbitrary without knowing the language's history.

Share this post


Link to post
Share on other sites
Quote:
Original post by Oluseyi
Quote:
Original post by F-Kop
Another thing that I often see questions about is hiding private members of classes. If there is no header to read, then all members are hidden. The only downfall to this is that documentation becomes more of a necessity for libraries that don't include source code.

You can ship C++ headers that exclude private members. They just provide interface declarations, after all.


Yep. You can do it in several ways, too.

1) Simply compile your code, then erase the private members from the header. Ship the modified header and the object file. This assumes that your intent is to provide a closed-source library.

2) Similarly, use the preprocessor:

foo.h

#ifndef FOO_H
#define FOO_H
class Foo {
#include "foo_internals.inl"
public:
// blahblahblah
};
#endif


foo_internals.inl

int spam;
float quux;
// etc.


3) If your intent is to provide open source but simply avoid long compile times in development (due to constantly changing the header to add or remove private data), use the pImpl idiom. If you want polymorphic behaviour anyway, you can get the pImpl benefits for free: just make the "public" class be a wrapper that delegates to a polymorphic "implementation" class that is held by pointer (as the sole private member of the wrapper). To get the right copy semantics for the wrapper, just use the_edd's value_ptr (an idea I independently came up with, but didn't develop nearly as far).

Share this post


Link to post
Share on other sites
Quote:
Original post by Bregma
What these languages had in common was that they produced code that ran on hardware. C++ is an extension of that legacy, and continues to use the same separate comilation model and continues to produce code that runs on hardware.

The single-pass compilation model is not required to produce code that runs on hardware. Neither is it required to consume object code created by other languages. The forward declaration requirement is an anachronism.

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman

1) Simply compile your code, then erase the private members from the header. Ship the modified header and the object file. This assumes that your intent is to provide a closed-source library.


Are you sure that will work? I thought client code needed to know the size of an object to instantiate it on the stack. If you do this, won't you get stack corruption? Please correct me if I'm wrong.

A better way (IMHO) is to provide an abstract class (and factory method) as an interface and derive the "real" class from that. Only ship the interface header.




Share this post


Link to post
Share on other sites
Quote:
Original post by ChaosEngine
Are you sure that will work? I thought client code needed to know the size of an object to instantiate it on the stack. If you do this, won't you get stack corruption? Please correct me if I'm wrong.

Yes, AFAIK you can only do this if the client is not allowed to instantiate the classes themselves (e.g. you could make the constructors private and provide a static Create method).

Quote:
Original post by ChaosEngine
A better way (IMHO) is to provide an abstract class (and factory method) as an interface and derive the "real" class from that. Only ship the interface header.

That approach has the same problem - the client still cannot instantiate the "real" class because they don't even know what it is!
(I guess the upside tho, is that the client can't even attempt to do so)

Share this post


Link to post
Share on other sites
Quote:
Original post by Hodgman
Quote:
Original post by ChaosEngine
A better way (IMHO) is to provide an abstract class (and factory method) as an interface and derive the "real" class from that. Only ship the interface header.

That approach has the same problem - the client still cannot instantiate the "real" class because they don't even know what it is!


That's what the factory method was for (i.e. a static "Create" as you said yourself). This is basically the COM model.

Share this post


Link to post
Share on other sites
Quote:
Original post by ChaosEngine
Quote:
Original post by Zahlman

1) Simply compile your code, then erase the private members from the header. Ship the modified header and the object file. This assumes that your intent is to provide a closed-source library.


Are you sure that will work? I thought client code needed to know the size of an object to instantiate it on the stack. If you do this, won't you get stack corruption? Please correct me if I'm wrong.


Er, yeah, like Hodgman said. I didn't really think that through. ^^;;

Quote:
A better way (IMHO) is to provide an abstract class (and factory method) as an interface and derive the "real" class from that. Only ship the interface header.


That's what the polymorphic pImpl approach does, except that additionally, the use of composition instead of inheritance (as well providing the correct copying semantics) allows the user to treat the instance as a value rather than a pointed-at thing. (Also, you can use normal constructor calls. [grin])

Share this post


Link to post
Share on other sites
Quote:
Original post by nobodynews
Quote:
Original post by F-Kop
I've been searching for a while for a new language to use, mostly for Win32 programs. The more I research, the more I realize that C++, which I've been using for years, fits my needs more than any other language out there. At least of the ones I've seen, which is a lot.


I'm sorry, I have to bring this up. This doesn't have a THING to do with your question so why did you include it? You could have simply left all that out, asked your question, and been done. But you did say it and now it has me wondering your needs are that you think only C++ can fill them. So, why C++?


That little introduction is sorta off topic, but my point was that I like a language that is object oriented, low-level (relatively), unmanaged, platform independent, and heavily supported. C++ meets all of these standards. Coding could be much quicker with a different model, but it would affect compile time. But compile time isn't all that important to me.

Share this post


Link to post
Share on other sites
Quote:
Original post by ChaosEngine
Quote:
Original post by Zahlman

1) Simply compile your code, then erase the private members from the header. Ship the modified header and the object file. This assumes that your intent is to provide a closed-source library.


Are you sure that will work? I thought client code needed to know the size of an object to instantiate it on the stack. If you do this, won't you get stack corruption? Please correct me if I'm wrong.

A better way (IMHO) is to provide an abstract class (and factory method) as an interface and derive the "real" class from that. Only ship the interface header.

That's basically what I did. But when it came to inheritance between interfaces, it means that you have to deal with multiple and virtual inheritance. For example, let's say there are two kinds of textures. ITexture and IDynamicTexture, and IDynamicTexture inherits from ITexture (since it "is-a" texture).

In the implementation, the CDynamicTexture_Impl class (or whatever you want to call it) will have to inherit from IDynamicTexture (so that it gets the IDynamicTexture and ITexture interfaces), and will also have to inherit from CTexture_Impl, so that it doesn't have to reimplement the ITexture interface. This is multiple inheritance, and the dreaded diamond, which you'll have to use virtual inheritance to solve.

This was the source of my problems in this thread. Now I'm redesigning my system so that it doesn't use this polymorphic pimpl approach.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this