C pre-processor or C++ magic to automatically make an object per file?

Viewed 120

I have a function whose behavior may need to be modified based on the file it is called from (e.g., to increase debugging trace output). This modification would need to be done without recompiling (e.g., by editing a configuration file or changing an environment variable).

Just as an example that would fill this need, I could write Function() as:

FunctionModifiable( const char* pszFile, int i );

Then make a macro thusly:

#define Function( i ) FunctionModifiable( __FILE__, (i) )

And FunctionModifiable() would have the duty to check pszFile in say an unordered_set<> that was populated during initialization to see if special functionality need be activated.

However, the overhead of that search is a minus (this is high-performance software and the function is called a huge number of times), and there is some per-file data that would need to be cached in this situation. We can eliminate the search, and have get storage for the cached info, by passing in not __FILE__ but a pointer to a helper object. This object needs the filename so that, when it undergoes one-off initialization, it can consult config or environment variables or what have you to know whether it needs special handling.

FunctionHelperObject fho( __FILE__ );

#define Function( i ) FunctionModifiable( &fho, (i) )  // C-style solution
#define Function( i ) fho.MethodModifiable( (i) )        // C++-style solution

OK, now say I want to avoid users having to define that fho in every file. (Inter alia, we can't re-write all existing files calling Function(), though say we're willing to recompile them).

The idea I had was the unusual step of putting a variable definition in the header file, so that any program including the header for Function() would get a FunctionHelperObject fho( __FILE__ ) for free. (Such definition would be #pragma once or guarded by a preprocessor variable.

The problem is that __FILE__ at that point would be the name of the header, not of the top-level compilation unit. If there was a __CFILE__ symbol, that would be the solution, but there's not.

Ultimately the best I can think of has shortcomings: 1) the "modifiable" aspect would only be available in source code explicitly written to use it, and 2) you'd have to do that explicit writing, and 3) starting to get a little complicated. In code you want to add the ability to modify behavior to you'd write USE_MODIFIABLE_FUNCTION somewhere after including the header in question. That'd be a macro that creates the FunctionHelperObject above, this time in the right "file" so __FILE__ would have the required value, and furthermore defines a macro as seen above that would mask the non-customizable function Function() with one of the two macros seen above. In short: the previous example, plus

#define USE_MODIFIABLE_FUNCTION FunctionHelperObject fho( __FILE__ );\n#define Function( i ) fho.MethodModifiable( (i) )

Code written without USE_MODIFIABLE_FUNCTION would simply call the uncustomizable Function() the normal way.

But surely this is some other accepted, portable way to provide this behavior? Although I've talked exclusively about the C preprocessor, is there perhaps any C++ template magic or any other approach that would work?

2 Answers

Cache the result.

// in the header with Function macro
static FunctionHelperObject functionhelper;
static inline void FunctionModifiableInterface(const char *file, int i) {
    static initialized = 0;
    if (initialized == 0) {
        initialized = 1;
        functionhelper = FunctionHelperObject(file);
    }
    FunctionModifiable(&functionhelper, i);
}
#define Function(i) FunctionModifiableInterface(__FILE__, (i))

You can't predict where the user would want to call you Function(i), so you can't predict the value of __FILE__. Just initialize it on the first call, which also is great, because you will not initialize it if Function is not called. You could do the same initialized check inside FunctionHelperObject constructor.

The really cool and hard to do trick is to modify your build system to allow you to pass a macro with the filename of compiled C file. Because build systems compile one C file at a time, it is possible (and it's a shame compilers doesn't do that by themselves). If you are using cmake with make backend (or really just make by itself), you could do something like this:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MY_FILE__='\"$(notdir $(abspath $<))\"'")

And then use FunctionHelperObject fho(__MY_FILE__) like you wanted to, because __MY_FILE__ depends only on the output filename from make.

One option is something like this (rough example, keeping the C++ syntax from the OP) :

#define Function(i) do { \
    static FunctionHelperObject obj(__FILE__); \
    FunctionModifiable(&obj, (i)); \
} while (0)

Note the above will have to be modified to accommodate for a return value if the function has one.

Also : an alternative to __FILE__ might be __func__ if that fits better with your needs.

Related