Jump to content
  • Advertisement
Sign in to follow this  
Kest

C++ Inlined code and extra header inclusion

This topic is 4233 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Is there any reason that header 1 needs to include header 2 in order to have inlined code in header 1 that accesses objects defined in header 2? Shouldn't there only be a reason when a specific source that includes header 1 uses the inlined code? Templated functions seem to do a great job at this. One could bypass the need to include headers in headers by simply defining an inlined code as a templated function. If your project is huge, the result is a big decrease in compile time. I guess this also goes with defining classes that include other objects as it's own members. Likewise, if these classes are templated, the headers that define them don't need to include information about their members. Is there any reason all inlined code shouldn't work as headache-free as templated code? And is there any way to trick the compiler into working that way without using templates?

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by Spoonbender
Um, could you show an example of what you mean, please?


Sure thing:


struct Object
{
int members;
int data;
int issue;

void Prep(int value, int arg, const SomeOtherObject &ref)
{
members = value;
data = arg * ref.data;
issue = ref.issue;
}
void Prep(int value, int arg, const YetAnotherObject &ref)
{
members = value;
data = arg * ref.data * ref.thing;
issue = ref.issue;
}
};


Many objects need to see "Object", but only one or two need to call Object::Prep(). So only those calling Prep() need exposed to SomeOtherObject and YetAnotherObject.

Share this post


Link to post
Share on other sites
Quote:
Original post by Kest
Is there any reason that header 1 needs to include header 2 in order to have inlined code in header 1 that accesses objects defined in header 2? Shouldn't there only be a reason when a specific source that includes header 1 uses the inlined code?


I think what you're trying to say is, "if I never use the inlined function within this translation unit, why does it matter if it's not fully defined", right?

You don't actually need to include "headers in headers", you just need to ensure that the source file has included both headers, in the correct order. Headers themselves are just injected text, after all, and have no intrinsic need to include anything.

Quote:
Templated functions seem to do a great job at this. One could bypass the need to include headers in headers by simply defining an inlined code as a templated function. If your project is huge, the result is a big decrease in compile time.


It's only a big decrease in compile time if you have a lot of inlined functions! And I don't think most people do.

Quote:
Is there any reason all inlined code shouldn't work as headache-free as templated code?


It would complicate the compilation process. :) I expect it's probably possible to treat an inline function that way but it makes work for the compiler developers and probably isn't what the C++ standard has specified before now. Basically the code must make sense in the context it's compiled in, whether it actually ever gets invoked or not.

Share this post


Link to post
Share on other sites
Quote:
Original post by KestMany objects need to see "Object", but only one or two need to call Object::Prep(). So only those calling Prep() need exposed to SomeOtherObject and YetAnotherObject.


There is no way to trick the compiler into not needing to have variable definitions in scope at compile time.

Template sort of allow something that appears to be similar on the surface, at least of you have a compiler that does not implement the C++ standard when it comes to 2-phase name lookup. Even templates will allow names not defined in scope only if they are dependant names.

The reason that you can't do this in C++ is called "strong typing." The problem it introduces is called "physical dependency." There are good reasons for the former and idiomatic techniques to work around the latter.

In general: avoid inline functions unless your profiling shows they contribute to a necessary performance improvement (avoid premature optimization at the price of maintainability).

Share this post


Link to post
Share on other sites
Quote:
Original post by Kylotan
Quote:
Original post by Kest
Is there any reason that header 1 needs to include header 2 in order to have inlined code in header 1 that accesses objects defined in header 2? Shouldn't there only be a reason when a specific source that includes header 1 uses the inlined code?


I think what you're trying to say is, "if I never use the inlined function within this translation unit, why does it matter if it's not fully defined", right?

Yes, that's what I meant. Sorry; too much caffeine.

Quote:
You don't actually need to include "headers in headers", you just need to ensure that the source file has included both headers, in the correct order. Headers themselves are just injected text, after all, and have no intrinsic need to include anything.

Well, what I meant was that any module that uses an object defined in a given header will need to be exposed to every object that is manipulated in that header, and every object that is used within other objects:
struct StructCar
{
StructSeat seat;
};
struct StructBox
{
void Paint(int r, int g, int b)
{ R = r; G = g; B = b; }
void Paint(const StructColor &color)
{ R = color.r; G = color.g; B = color.b; }
};


Even the modules that don't define an instance of StructCar will still need exposed to StructSeat if they see this header. And even though few modules may use the StructBox::Paint() that accepts StructColor, and even though StructBox doesn't require StructPaint, to exist and function, all modules that use StructBox will need exposed to StructPaint, since the 'convenience' code to pass colors is inlined.

Quote:
It's only a big decrease in compile time if you have a lot of inlined functions! And I don't think most people do.

Well, definitely relative. But just one inlined function can expose another entire module. And if that exposed module has a single inline function, it can expose another, and so on and so forth.

Quote:
Quote:
Is there any reason all inlined code shouldn't work as headache-free as templated code?


It would complicate the compilation process. :) I expect it's probably possible to treat an inline function that way but it makes work for the compiler developers and probably isn't what the C++ standard has specified before now. Basically the code must make sense in the context it's compiled in, whether it actually ever gets invoked or not.

That's all I needed to understand. That the sharp pains are necessary. Thanks for enlightening me.

I think my real problem may be the size of my project. Is it common to define sets of small objects in general headers? Like defining a Ray-Intersect test structure in the same "CollisionObjects.h" header with a Moving-Sphere test structure? I'm already drowning in so many modules and headers, I can't imagine how more could be better. I mean it's far better than having so many object definitions crambed into one module that it's difficult to navigate. But the other extreme seems bad too. 230 files so far, and guessing about half are headers.

Quote:
Original post by Bregma
In general: avoid inline functions unless your profiling shows they contribute to a necessary performance improvement (avoid premature optimization at the price of maintainability).

Ah. I'm not inlining to increase performance. Just to avoid having small simple one-liner routines exist in two different places.

Share this post


Link to post
Share on other sites
I understand your question (after restated by Kylotan) and I somewhat understand your interest in not having the compiler having to know about things is doesn't really need to know about (if the language had been designed differently maybe).

But I get the feeling you have a little something strange in the way you do things (or think they are working).

1. In general in C++ a thing needs to be known BEFORE it is used ... as in lexically above its reference.

So you can call a function without prototypes if you are in code AFTER the function is defined, but not before. You can only instantiated classes which are ALREADY defined etc. If you only need a pointer to a thing, then all the compiler needs to know is what type of thing it is (for instance using a forward declaration for a class instead of #including the full definition).

So, for any line of code, anywhere in the program to work (compile) the compiler needs to have PREVIOSLY been exposed to it. Classes, templates, fucntions and inline functions all work the same way. In fact inlined function are just like templates (but are not like functions INSIDE OF templates). To use a templated classes, you must have the WHOLE template definition included into each modules source. Its just that the function down inside of the template do not get compiled unless used - but the template (the class) does get parsed / compiled.

Now, if you have written your headers the best possible way, each header #includes exactly those things it absolutely needs, and forward declares those things which it uses, but does not need definitions for. In this way, you should never need to #include extra files for things you are not directly using in your code, but you also shouldn't be compiling any slower than necessary. (for example, if I use std::map I don't include the red-black tree header the implementer used to code it, map does that for itself and I'm completely unaffected by changes from 1 version to the next where the implementation uses more or less files to organize the code needed by a std::map (assuming they do not pollute any namespace I am using - but I'm never supposed to put my own code inside of namespace std just for that reason).

Now of course, if you have 200 files to compile, then the compiler has to reparse and use each of the header each time - unless it can somehow be smarter than that. Which is where precompiled headers come in. For most projects there is a set of files which have absolutely no collisions (no redefining the same token two different ways - at least within the same namespace) - and in those cases you can just take all the files which are includable (or any subset of them) and put them into a precompiled header, so that the compiler reads the whole damn mess of them once, and builds its gigantic symbol table, and them proceeds to compile each translation unit (non-header source file in most cases) starting from that remembered symbol table. In this way more time and memory is spent building the monolithic symbol table (precompiled header file), but very little duplication of effort is done while compiling.

Now 1 more thing about your functions. You do realize that besides compile time, there is no cost in having the function definitions #included into every file in your project ... one of the linker's jobs is to remove duplicate code from the output ... and when possible it even remove ununsed code as well (just like unused template functions). So if you compile an exe (not a library) in release mode - that exe should not contain function bodies for unused functions, and should never contain code duplication except where the compiler / optimizer determined it was efficient to do so.

Share this post


Link to post
Share on other sites
Quote:
Original post by Kest
struct StructCar
{
StructSeat seat;
};
struct StructBox
{
void Paint(int r, int g, int b)
{ R = r; G = g; B = b; }
void Paint(const StructColor &color)
{ R = color.r; G = color.g; B = color.b; }
};


Even the modules that don't define an instance of StructCar will still need exposed to StructSeat if they see this header. And even though few modules may use the StructBox::Paint() that accepts StructColor, and even though StructBox doesn't require StructPaint, to exist and function, all modules that use StructBox will need exposed to StructPaint, since the 'convenience' code to pass colors is inlined.

Well, it's hard to inline code if you can't see the code. That's the compiler's problem. How do you expect it to inline a function call, if it can't see what *happens* at that function call? That should answer your Paint() problem. Don't declare it inline like this if you want to constrain visibility.

And the obvious question for your StructCar is, why is it in the same header as StructBox, if they don't depend on each others, and (some) files only need one of them?

Also the PIMPL idiom might be able to help you in some cases. It allows you to specify the public interface for a class without having to expose member variables and other internal fluff.

Quote:

Well, definitely relative. But just one inlined function can expose another entire module. And if that exposed module has a single inline function, it can expose another, and so on and so forth.

So don't declare functions inline if they're big enough to do that. You generally only want to explicitly inline small functions.

Furthermore, in some (many) cases, you can get away with forward declarations, instead of having to include the header in question.

Quote:
Like defining a Ray-Intersect test structure in the same "CollisionObjects.h" header with a Moving-Sphere test structure?

Only if you want both to be included together, with the effect on compile-time that it has.
Quote:

I'm already drowning in so many modules and headers, I can't imagine how more could be better.

Subfolders? [wink]

Share this post


Link to post
Share on other sites
Quote:
Original post by Spoonbender
Well, it's hard to inline code if you can't see the code. That's the compiler's problem. How do you expect it to inline a function call, if it can't see what *happens* at that function call?

Every module that sees the class does not inline the code. If hundreds of modules use the class, and only two inline the code, then hundreds minus two needed exposed for no reason. I just don't see any advantage to having it behave that way.

Quote:
That should answer your Paint() problem. Don't declare it inline like this if you want to constrain visibility.

That I understand. But don't think it should be necessary.

Quote:
And the obvious question for your StructCar is, why is it in the same header as StructBox, if they don't depend on each others, and (some) files only need one of them?

They aren't. That was just two different examples of seperate but related issues.

Quote:
Quote:
Well, definitely relative. But just one inlined function can expose another entire module. And if that exposed module has a single inline function, it can expose another, and so on and so forth.

So don't declare functions inline if they're big enough to do that. You generally only want to explicitly inline small functions.

You must have misunderstood me. I meant small functions. My Paint() function was very small, no? If the StructPaint class had a similar inlined function that used yet another object, then three headers were included where only one was really needed (in some modules).

Quote:
Original post by Xai
1. In general in C++ a thing needs to be known BEFORE it is used ... as in lexically above its reference.

C++ needs them to be known even in cases where they aren't used at all in a given module. I would rather only need to expose other objects to modules that use the functions that manipulate those other objects.

If a specific module doesn't use any functions that manipulate StructPaint objects, then it doesn't need exposed to the StructPaint object, regardless of it being passed to an inlined function that other modules use. In C++, it does need exposed. Unless you make it templated. And by making a class templated, you make it only necessary to expose the objects that are used in a specific module to that module. I understand why templates need to behave this way. I just wish other code did the same. Or at least I don't think there would be any negative reasons to not have it behave that way.

Quote:
So, for any line of code, anywhere in the program to work (compile) the compiler needs to have PREVIOSLY been exposed to it.

I understand that. That's not what the problem is. I'm talking about modules that don't use any code which accesses certain objects, but still needs exposed to them.

Share this post


Link to post
Share on other sites
In C++, they do NOT need to know about things that aren't used. But they do need to know about everything that is used, no matter who is doing the using.

If I write functions DoSomethingCool and DoSomethingCooler and put them in the same header and same cpp file. Then I have another file which needs to call DoSomethingCool. It in no way needs to know anything about DoSomethingCooler, templated or not. It only needs DoSomethingCool. The only reason it knows about DoSomethingCooler is because I'm a lazy programmer who'd rather put both functions into a single easy-to-remember header file than make a header file per function and just include the ones I use. If I wanted I could put 200 functions in 1 cpp file, but 200 seperate headers for their prototypes, I could do that. Or I could even just retype their prototypes directly immediately above the line of code where I call it (in fact all #include is doing is the same as if you typed it there yourself).

But the thing is, to compile the BODY of something, such as a function or class, then you need to know everything it calls / uses too.

So in your example, someone who uses StructCar needs to know everything that StructCar needs to know. I mean how could a compiler generate a call to StructCar.Foo without knowing if Foo is declared as a virtual function inside of StructCar's base class StructObject.

The beauty of the C++ #include system is that it IS a minimal system. If used correctly. Certain things belong in headers and certain things don't based exactly on whether or not client code needs to know it, and whether or not it is safe (or desirable) to compile duplicates of it into multiple modules.

By definition, inline function DO want to be duplicated in every modules they are used in, so thats the scope of when they must be #included.

Class definitions are similar to function definitions, its all or nothing, you can declare a class or define a class, but you cannot define a class in seperate pieces some here, some their. It must be fully defined 1 way in its entirety.

So, think for a second about your example

StructCar uses StructSeat.

If client code A wants to make a StructCar and call its functions and have them work, the compiler must know about StructSeat to do so (for instance if it is a base class, if it is a class member, if it is a parameter to a function, etc). The compiler cannot compile:

int Foo(int i)
{
return i + 2;
}

if it doesn't know what an "int" is.

so likewise it cannot compile:

class StructCar
{
StructSeat seat;
};

without knowing what a StructSeat is.

but, that said, the C++ include system rocks, because the ONLY usefull way to write the StructCar header file is to #include exactly those things needed by the StructCar header file, and therefore when a client "Module A" want to do this:

StructCar car1 = new StructCar();
scene.Add(car1);

it only has to #include "StructCar.h"

and never ever every does the programmer need to know that a StructSeat even exists (the compiler needs to know, but the programmer does not).

So the organization of a third party library does not affect the client developer except in locating the class definitions you DIRECTLY want to use. All indirect dependencies will be handled by the header file system automagically.

It is actually a near perfect system given a few of the constraints it wanted to support.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!