28

I'm used to this:

class Db {
  _Commit(char *file, int line) {
    Log("Commit called from %s:%d", file, line);
  }
};

#define Commit() _Commit(__FILE__, __LINE__)

but the big problem is that I redefine the word Commit globally, and in a 400k lines application framework it's a problem. And I don't want to use a specific word like DbCommit: I dislike redundancies like db->DbCommit(), or to pass the values manually everywhere: db->Commit(__FILE__, __LINE__) is worst.

So, any advice?

4
  • 15
    Don't use the name _Commit. Beginning with an underscore and having a capital letter as its second character, it is reserved for the implementation, and your using it means your program has undefined behavior. Commented Nov 16, 2010 at 16:40
  • 4
    in other words, the compiler is allowed to define a macro named _Commit, which would break your code. Commented Nov 16, 2010 at 17:04
  • Is it allowable to be non-portable and having to lookup function addresses manually or via a script using addr2line? In that case you could use GCC's __builtin_return_address. Logging addresses instead of names isn't pretty, but presumably you only need to know when something went wrong (which means, rarely), so that might do. The nice thing is that it "just works". I'm passing the last up-to-three callers in exceptions, which works fine (apart from being unwieldy to decipher) too. Commented Feb 26, 2015 at 15:11
  • Guess what. It can't be done. Both FILE and LINE are macros, and have to be invoked from a macro-processing context to have the effect you require.. Commented Mar 2, 2016 at 11:11

3 Answers 3

52

So, you're looking to do logging (or something) with file & line info, and you would rather not use macros, right?

At the end of the day, it simply can't be done in C++. No matter what mechanism you chose -- be that inline functions, templates, default parameters, or something else -- if you don't use a macro, you'll simply end up with the filename & linenumber of the logging function, rather than the call point.

Use macros. This is one place where they are really not replaceable.

EDIT:

Even the C++ FAQ says that macros are sometimes the lesser of two evils.

EDIT2:

As Nathon says in the comments below, in cases where you do use macros, it's best to be explicit about it. Give your macros macro-y names, like COMMIT() rather than Commit(). This will make it clear to maintainers & debuggers that there's a macro call going on, and it should help in most cases to avoid collisions. Both good things.

Sign up to request clarification or add additional context in comments.

9 Comments

+1 -- I can't believe I'm upvoting an answer saying "use macros", but +1 in any case.
You may have to use a macro, but that doesn't mean you can't be explicit about it. COMMIT() is obviously a macro call (by normal conventions) and so oughtn't to conflict with anything in your 400kloc application framework.
@Nocola: Readability is enhanced by letting your users know it is a macro, not a function. Otherwise they may try to use the macro in places where it wouldn't make sense (such as attempting to create a function pointer to it)(
as @Billy says, readability is to do with being able to understand the code you're looking at, and making your macros look like macros is an important part of that.
@Nicola In the case of a macro, you want to be shouting "THIS IS A MACRO". Macros are inherently dangerous, and should be marked as such.
|
7

Wait till C++20, you cal use source_location

https://en.cppreference.com/w/cpp/utility/source_location

1 Comment

Pre-C++20 some compilers had builtins that could be used in the same manner, e.g. __builtin_FILE() in GCC.
0

You can use a combination of default parameter and preprocessor trick to pass the caller file to a functions. It is the following:

  1. Function declaration:

    static const char *db_caller_file = CALLER_FILE;
    
    class Db {
        _Commit(const char *file = db_caller_file) {
        Log("Commit called from %s", file);
      }
    };
    
  2. Declare db_caller_file variable in the class header file. Each translation unit will have a const char *db_caller_file. It is static, so it will not interfere between translation units. (No multiple declarations).

  3. Now the CALLER_FILE thing, it is a macro and will be generated from gcc's command line parameters. Actually if using automated Make system, where there is generic rule for source files, it is a lot easier: You can add a rule to define macro with the file's name as a value. For example:

    CFLAGS= -MMD -MF $(DEPS_DIR)/$<.d  -Wall -D'CALLER_FILE="$<"'
    

-D defines a macro, before compiling this file. $< is Make's substitution for the name of the prerequisite for the rule, which in this case is the name of the source file. So, each translation unit will have it's own db_caller_file variable with value a string, containing file's name.

The same idea cannot be applied for the caller line, because each call in the same translation unit should have different line numbers.

1 Comment

... but we compile translation units, not files. This should not be working, I'm afraid.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.