Sign in to follow this  
beebs1

Empty Functions

Recommended Posts

Hiya, Does anyone know if MSVS 2005 will just omit any function calls to empty functions when it compiles a program? I know it's a bit of a strange question - the reason behind it is I'd like to take advantage of the OutputDebugString() function like so:
void DebugOut( const wchar_t *format, ... )
{

    #ifdef _DEBUG

    // use va_start() etc. to build the string

    OutputDebugString( msgbuf );

    #endif

}
I just want to check it won't cost me anything in a release build. Thanks for any help [smile]

Share this post


Link to post
Share on other sites
You should be using #Ifdef _Debug on the calls to Debugout, not on the debug function. From my understanding there will still be a call to the function even though it is empty.

theTroll

Share this post


Link to post
Share on other sites
An empty inline function is ignored, yes. If it isn't inline, the linker may choose to ignore it, but it's not certain.

The arguments to the function will still be evaluated if there is any chance that they have a side-effect.

Share this post


Link to post
Share on other sites
OK, thanks. I can inline it, but I suppose there's no guarantee it'll actually be inlined by the compiler. Coudld you suggest another method?

I was going to use it like this:

try {
// all these may throw
pfnInitRenderer( ... );
CreateWindow( ... );
AdjustWindow( ... );

// etc...
}
catch( const wchar_t *pReasonForThrow )
{

// this generates warning C4101 - uninitialized local variable
#ifdef _DEBUG
DebugOut( L"Error: \s. Renderer::Initialize() fails.\n" );
#endif

}


I could disable the warning, but I'd much rather not have to, I guess I'm strange like that.

Thanks again.

Share this post


Link to post
Share on other sites
Well, you are weird about that warning deal [smile] Have you corrected what was causing it? In practice, getting this warning because of a conditional compilation of a logging statement is bad news.

Share this post


Link to post
Share on other sites
I prefer the word 'special' [smile] But more seriously...

Sorry - C4101 is an unreferenced local variable, it's caused because pReasonForThrow is never used in the release build. I guess I'll have to deal with an empty function call for now - at the end of the day, if an exception's been thrown anyway a tiny bit more overhead won't make a difference. It just seems a bit ugly.

Thanks for your help.

Share this post


Link to post
Share on other sites
I'm assuming I'm well off-base here, but since the preprocessor is just straight text-replacement, couldn't you go with:

#ifdef _DEBUG
try {
#endif
// all these may throw
pfnInitRenderer( ... );
CreateWindow( ... );
AdjustWindow( ... );

// etc...
#ifdef _DEBUG
}
catch( const wchar_t *pReasonForThrow )
{
DebugOut( L"Error: \s. Renderer::Initialize() fails.\n" );
}
#endif


and, outside debug mode, let the exception propagate (hopefully) to something that can handle it, rather than pretending to handle it -- which seems a bit messy, to me? Although, I guess, you then end up sprinkling '#ifdef'/'#endif' all over the place.

(Woah, that's one serious run-on sentence)

Share this post


Link to post
Share on other sites
or just:


#ifdef _DEBUG
#define DebugWhatever( arguments )
MyDebugFunction( arguments )
#else
#define DebugWhatever( arguments ) ""
#endif



my syntax is horribly off, but i don't use macros frequently enough... but you get the idea. In debug configuration your call to the debug function gets written in to the code, if not debug then it just gets changes to whitespace.

-me

Share this post


Link to post
Share on other sites
Quote:
Original post by Palidine
or just:

*** Source Snippet Removed ***

my syntax is horribly off, but i don't use macros frequently enough... but you get the idea. In debug configuration your call to the debug function gets written in to the code, if not debug then it just gets changes to whitespace.

-me


Close enough, just #define it to nothing:

#ifdef _DEBUG
#define DebugWhatever( arguments ) \
MyDebugFunction( arguments )
#else
#define DebugWhatever( arguments )
#endif


Share this post


Link to post
Share on other sites
I'd recommend using a macro to conditionally compile out the call.

#ifdef DEBUG && WIN32
#define WriteLog_(...) ::OutputDebugString(__VAR_ARGS__)
#else
#define WriteLog_(...)
#endif // DEBUG && WIN32

Variable argument macro's dont work on all compilers so there are other tricks you can do to support them if you need to make this work on multiple compilers.

Also, exception handling is a form of error management so having an empty catch in release doesn't sound like the best idea to me.

Share this post


Link to post
Share on other sites

#include <sstream>
#include <iostream>

template < bool Enabled >
class Appender
{
public:
Appender( void ) {}
~Appender( void )
{
std::cout << buf.str() << std::endl;
//OutputDebugString( buf.str().c_str() );
}

template < class T >
inline Appender<Enabled> & operator<<( const T &t ) {
buf << t;
return *this;
}
private:
std::ostringstream buf;
};

template <>
class Appender<false>
{
public:
template < class T >
Appender<false> & operator<<( const T &t ) {
return *this;
}
};

#ifdef _DEBUG
typedef Appender<true> DebugOut;
#else
typedef Appender<false> DebugOut;
#endif

int main(int argc, char* argv[])
{
int x = 10;
DebugOut() << x << "Hello" << 15 << 'a';
std::cout << "--------" << std::endl;
DebugOut() << x << "Hello" << 15 << 'a';
}







This doesn't generate code in MVS release build, with possible exception of functions that would get called as parameters for the output (even there, they might not get compiled, since compiler is smart enough to figure out that some actions don't have side-effects).

Share this post


Link to post
Share on other sites
Thanks, the __VA_ARGS__ macro was exactly what I needed.

David, you're right about the catch handler - it's not really empty.

Many thanks for the quick replies :)

[Edit] Too slow for Antheus' post. That's pretty clever...

[Edit 2] In fact, that's too clever - why do you declare Appender twice, and why do you not need to declare an instance of Appender/DebugOut to use it? You seem to just use it like a constructor... is that a functor? I'd appreciate it if you could explain more :)

[Edited by - beebs1 on July 10, 2007 6:46:59 PM]

Share this post


Link to post
Share on other sites
One of the templates he wrote is specialised to "false".

All other values of bool will cause the first template to be instantiated.

The false version doesn't output anything because it changes how operator<< oworks with objects of Appender<false>.

The instance is created every time you call the macro. The macro expands to either

Appender<true>() << x << "Hello" << 15 << 'a';
// or
Appender<false>() << x << "Hello" << 15 << 'a';


Share this post


Link to post
Share on other sites
Quote:
[Edit 2] In fact, that's too clever - why do you declare Appender twice, and why do you not need to declare an instance of Appender/DebugOut to use it? You seem to just use it like a constructor... is that a functor? I'd appreciate it if you could explain more :)


An object declared will be destroyed when it goes out of scope.


{
Object x; <-- Object::Object( void ) is called
...
...
} <-- Object::~Object() is called





If the compiler implements the standard correctly (VC6 likely doesn't, didn't test), then object not assigned to a variable will go out of scope as soon as the ; is encountered;

Object() <-- Object::Object( void ) is called
<< x <-- << returns reference to above object
<< y <-- same here
; <-- The reference is not assigned anywhere,
it becomes inaccessible and goes out of scope,
Object::~Object() is called





I won't guarantee incredible portability, but it should work with modern compilers.

Also, due to compiler knowing about all the types, the object in-place creation and destruction is essentially free, especially if compiler performs inline expansion. So despite objects and functions, this expands into the following:

std::ostringstream buf;
buf << x;
buf << y;
std::cout << buf.str();




Or, in case of disabled output, it expands into this:

// empty constructor
x; // not assigned anywhere (1)
y; // not assigned anywhere (1)
// empty destructor



At (1) is where a function might get evaluated, if whatever value you're outputting causes side-effects. But knowing the return value isn't assigned anywhere, the compiler will probably be smart enough and not evaluate it unless the function affects some other state (probably performs a write).

Share this post


Link to post
Share on other sites
1) Antheus' techniques are considered pretty standard now. Although I would object to the use of (void) prototypes, preferring () in modern C++. Note that template classes are specialized as an all-or-nothing deal, so Appender<false> actually has no data members and a do-nothing ctor and dtor (even if the compiler is too stupid to optimize it all out).

2) Notwithstanding all the preprocessor mumbo-jumbo, the standard way (in C++) to avoid warnings about unused locals is to not give them a name. Because they then *can't* be used (and yes, it's perfectly legal - you only need to mention the type in a catch statement - or for that matter, in a function argument list), the compiler will shut up.

Share this post


Link to post
Share on other sites
Quote:
Although I would object to the use of (void) prototypes, preferring () in modern C++.


I'm consistent with using (void) in function/method declarations.

It gives me an extra check to avoid errors like this.

The safety comes from knowing that if any declaration of a method or function call will need (void), or it's operator(), which I frequently use.

It's a style thing. It may add some clutter, but has no side-effects. In heavily templated projects with lots of functors, this can help a bit.

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