Quote:Original post by Cedric Perthuis
it's easy to conceive a significant percentage of virtual function call will miss the instruction cache, just because there are 2 indirections, a far bigger percentage than if there was no virtual function, because in that case the compiler or the user could rearrange the functions.
It's easy to conceive, but wrong in this situation. There is one additional indirection here, which consists in jumping into a vtable and fetching an address there. In themselves, vtables are pretty small and only a few different ones exist.
In short, if you call a small group of virtual functions 5000 times in a row, the processor would probably cache-miss on the first access to each vtable, cache it, and hit the cache for the others. So, the more you call your virtual functions, the more they behave like normal ones. Of course, if you flush the cache often enough for vtable access to be performance-adverse, you've got bigger problems on your hands than virtual function overhead (that is, the cache flushing itself).
On the other hand, if you do need runtime polymorphism, you'll have to pay for it (either as virtual functions, or as your own in-house approach, which is usually a bad starting point), and if you don't need runtime polymorphism, you shouldn't be using virtual functions at all.
Quote:Original post by Cedric Perthuis
it's a good idea. the only little disadvantage, is that this is one additional function call, which is difficult to inline if you keep the class definition in a .h and the implementation in a .cpp ( if I am correct only visual can do it at link time, not sure ). but it's probably not as bad as the virtual funtion for the instuction cache.
If you choose not to provide implementation details in the header file, then you will
always have this problem: the definition will not be available for inlining. So, if your intent is to hide a high-performance tool this way, too bad.
Note that additional function calls made inside the implementation can be inlined: it's only the interface functions that cannot be.
And now, for something completely different: a variation on the pimpl idiom, with exactly zero strategy-specific implementation details in the interface.
// renderer_facade.hpp// No private strategy detailstemplate <class RenderingStrategy>class RendererFacade{ RenderingStrategy::RendererImpl impl;public: void Render() { impl.Render(); } Texture<RenderingStrategy> Load(const std::string& file) { return impl.Load(file); }};template <class RenderingStrategy>class TextureFacade{ RenderingStrategy::TextureImpl impl;public: // And so on};// GLRenderer.hpp// Private details for GL strategy only#include "renderer_facade.hpp"class GLStrategy {public: // Renderer implementation class RendererImpl { // Implementation data here public: void Render(); Texture<GLStrategy> Load(const std::string& file); }; // Texture implementation class TextureImpl { // Other members };};// renderer.hpp#ifdef OPENGL #include "GLRenderer.hpp" typedef GLStrategy RendererStrategy;#elif DIRECTX #include "DXRenderer.hpp" typedef DXStrategy RendererStrategy;#endiftypedef RendererFacade<RendererStrategy> Renderer;typedef TextureFacade<RendererStrategy> Texture;
The benefits of this are:
- Anything that would have been inlined in a straightforward implementation will be inlined here (including all the wrapper calls) on any decent optimizer.
- The interface is split into two files: a strategy-agnostic main file, which merely describes the interface (your public header file), and a strategy-specific helper file that discusses actual data members (your private header file).
- It's automatic: the size of the Renderer class is adjusted by the compiler, its constructors, destructors and assignment operators are safely forwarded, and you can alter the private implementation without having to refer to the standard every few seconds.
- You can add new renderers without having to modify the facade class: you only have to alter the compiler switch in renderer.hpp to accomodate a new typedef.
- You can ensure consistency: an OpenGL Renderer cannot be mistakenly given a DirectX Texture to work on.
- You have an interface-implementation separation for each strategy, so changing a strategy doesn't force you to recompile everything.
I usually refer to
C++ purists as
C++ victims: people who've been bitten in the ass by C++ so many times that they can actually
smell a bad idea when they see one. Your quote:
Quote:So I was thinking that an idea to try with extreme care in some very specific cases, would be to create those 2 versions of the headers, a public only one, and the regular one ( public and private ). the lib user could compile with the public one only. The link step will still be correct. In most cases I think it will just work.
is enough to send shivers down my spine, my chair, the floor in my room, plus one or two additional floors in my apartment building all down to the basement, and keep'em there for an hour or two. Knowing that when this kind of contraption blows up (and it always does), you'll be out in the cold, naked (without a compiler/debugger/linker/standard to help you out).