26

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?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Nicola Leoni
  • 794
  • 1
  • 5
  • 12
  • 14
    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. – R.. GitHub STOP HELPING ICE Nov 16 '10 at 16:40
  • 4
    in other words, the compiler is allowed to define a macro named `_Commit`, which would break your code. – jalf Nov 16 '10 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](https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html). 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. – Damon Feb 26 '15 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.. – user207421 Mar 02 '16 at 11:11

3 Answers3

51

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.

user229044
  • 232,980
  • 40
  • 330
  • 338
John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • 4
    +1 -- I can't believe I'm upvoting an answer saying "use macros", but +1 in any case. – Billy ONeal Nov 16 '10 at 16:35
  • 11
    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. – nmichaels Nov 16 '10 at 16:37
  • mmm, I've imagined that. So a common problem, but legacy solutions. I must use the ugly DbCommit. I dislike COMMIT, because is a library problem if the library needs to use a macro or not, in the application code I want prefer readability. – Nicola Leoni Nov 16 '10 at 16:50
  • 8
    @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)( – Billy ONeal Nov 16 '10 at 16:55
  • 1
    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. – jalf Nov 16 '10 at 17:05
  • Yes. I know but I always disliked upper case macros, they are so unaesthetic, like people that WRITES POSTS OR CHATS IN UPPER CASE :P – Nicola Leoni Nov 17 '10 at 10:35
  • 2
    @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. – KeithB Nov 17 '10 at 19:53
  • 1
    It is interesting, why there is no such feature in C++. It will work like default parameters. Like having: void commit (int a, int b=2) we could have void commit (int a, int b=2, string file=_CALLER::FILE, int line=_CALLER::LINE) and in each call the compiler could replace the special _CALLER members with the file and line, where the call is made - the same way with default parameters, but the value is not constant - it depends on the source code location - things, that the compiler knows, while parsing a CPP file, Any idea, why such thing does not exist? – Nuclear Apr 11 '14 at 10:29
  • Late to the party, but I would surely use some sort of "module prefix" for my macro because other people in your distributed development team, or just third parties, just *might* have the same idea -- and they shouldn't use COMMIT either! The word "commit" may be used outside of database contexts, or for different databases. So for example at least CUST_DB_COMMIT(), or even MYCOMP[ANY]_CUST_DB_COMMIT(). – Peter - Reinstate Monica Jan 24 '23 at 09:46
6

Wait till C++20, you cal use source_location

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

user3770640
  • 61
  • 1
  • 1
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.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Nuclear
  • 1,316
  • 1
  • 11
  • 17