1

I used to logging in C with variable amount of arguments and formatting, and I wanna how Can I meet this in C++.

Through Q&A like this (How to make a variadic macro for std::cout?), I know how to handle variable amount. But what I still do not know is, how to format, cause I can not use methods like 'setbase' between arguments now.

For example:

// in C
#define err(fmt, ...) (printf("[%s] "fmt"\n", __FUNCTION__, ##__VA_ARGS__))
#define FATAL(fmt, ...) do{\
    err(fmt, ##__VA_ARGS__);\
    CLEAN_UP;\
    exit(1);\
    }while(0)

int main(){
  if(1) FATAL("Just a test: 0x%lX, %d", 1, 2);
  return 0;
}

"FATAL" here, accept variable amount of arguments with formatting, print them, and do some extra. I have no idea how to declare such a "FATAL" in C++.

Community
  • 1
  • 1
Galaxy
  • 1,129
  • 11
  • 27
  • 1
    Don't try to implement C-style code in C++.. In C++ you should rely on exceptions to report an error. Then again macros should be generally avoided in C++ - prefer ordinary functions. – Marinos K Mar 05 '16 at 07:15
  • I wanna print variable arguments with formatting without macros like I did in C, that's why I ask for a C++ styled way. @MarinosK – Galaxy Mar 05 '16 at 07:20
  • So, you want to use techniques that you use in C, even though there are various techniques in C++ that are usually recommended because they are type safe (among other things) and considered preferable? – Peter Mar 05 '16 at 07:32
  • Why would you not be able to use `setbase`? – Zulan Mar 05 '16 at 07:40
  • 1
    If your example is irrelevant then please **remove** it. Add a relevant example. – Cheers and hth. - Alf Mar 05 '16 at 07:57
  • @Zulan : Seems like, if I implement a method log_add_more, which can be used this way: log_and_more << 1 << setbase(16) << 2, then there would be no more place for other actions after std::cout in log_and_more. Or if I implement as ( http://stackoverflow.com/questions/29326460/how-to-make-a-variadic-macro-for-stdcout ) suggested, then setbase(16) should not appear among the parameters. – Galaxy Mar 05 '16 at 08:19
  • Have a look at http://www.boost.org/doc/libs/1_60_0/libs/format/doc/format.html – Christian Hackl Mar 05 '16 at 09:54

4 Answers4

3

You can achieve that by using the operator<< and a custom destructor on an ad-hoc logging object.

class log_error
{
public:
    log_error() = default;
    log_error(log_error&& other) = default;
    ~log_error()
    {
        // Do whatever you want with the input
        // Add a timestamp, process/thread id
        // Write it to a file, send it to a server ...
        std::cerr << "[ERROR] " << ss.str() << std::endl;
        throw std::runtime_error(ss.str());
    }

    std::stringstream ss;
};

template<typename  T>
log_error operator<<(log_error&& le, const T& t)
{
    le.ss << t;
    return std::move(le);
}

I only included the essentials for basic usage. For more complex usage you want to consider a copy variant of the ctor / operator<<.

The usage is very idiomatic C++. But you have to remember the ():

log_error() << "Ooops " << 23 << ", 0x" << std::setbase(16) << 23;

This line will print out the message and throw an exception.

You can customize this however you want. Write to logfiles, add timestamps or other helpful information, verbosity levels and thresholds. It is even possible to have most cases completely optimized out in production builds.

Live example

Zulan
  • 21,896
  • 6
  • 49
  • 109
  • @hxpax can you explain how this does not satisfy your requirements? – Zulan Mar 05 '16 at 15:50
  • It is stuck with Exception. Sometimes I just want to log error messages; sometimes I want to log the messages and do some extra actions except exit, like {log; act1; act2;}, and this may be invoked many times so I wanna wrap them together. – Galaxy Mar 05 '16 at 16:06
  • Any way your anwser is good and very informative, I've learned a lot from, but Serge's answer is more competent here. – Galaxy Mar 05 '16 at 16:10
  • @hxpax, you can do whatever you please in the destructor. You don't have to use exceptions. I was hoping that was clear from the comment. – Zulan Mar 05 '16 at 22:59
1

C++ is not C! While you can use C-style (and often C) code this is not advisable. Firstly you should not normally rely on macros as they violate the type system, use (possibly inlined or constexpr) functions instead. Then you should not use C-style error handling technique, use exceptions instead. I'd also recommend against variadic arguments in general and finally you don't need C-style string formatting techniques -> this is C++, use stringstreams to format your code.

In your particular case I'd do something like this:

#include <exception>
#include <iostream>
#include <sstream>
#include <string>

inline void fatal(std::string msg) {
  // clean_up
  throw std::runtime_error(msg);
}

int main(){
  std::ostringstream msg;
  msg << "Just a test: " << 1 << 2;
  if(1) fatal(msg.str());
  return 0;
}
Marinos K
  • 1,779
  • 16
  • 39
  • This has the cost of doing formatting even if there is no error. – Yakk - Adam Nevraumont Mar 05 '16 at 07:24
  • No it doesn't, this is a silly example - in production code you would only format your code in some `catch` or `if` clause and only when some error occurs.. it's no different than having the formatting inside the function. – Marinos K Mar 05 '16 at 07:26
  • @Yakk my example is meant for demonstration only and is basically a C++ version of the code in the original question. – Marinos K Mar 05 '16 at 07:53
  • Any way without exception? I want to avoid them in low level coding, cause in my option they r kinda slow. – Galaxy Mar 05 '16 at 08:29
  • they're not that slow and they give you a guarantee that all the destructors will be properly called.. else you can always do it like you would do in C, but why use C++ then? – Marinos K Mar 05 '16 at 08:56
  • 1
    @hxpax Exception have zero overhead for the normal, non-throwing execution path. There is an overhead when they are thrown, but that should not matter because this situation is exceptional and thus does not impact your performance. It can be a problem for hard real-time systems because you cannot guarantee runtime in the exceptional path. – Jens Mar 05 '16 at 09:03
1

I also have to point out that C++ and C are two different languages with different patterns and idioms. C++ has better alternatives for many C constructs which are more type-safe and thus preferable. IN your case, I would throw an exception in this case. If you ban catch(...) in your code, it will terminate your program. When the exception is propagated, the compiler will also call destructors of objects and thus do clean-up. If you haven't, I recommend you read up on resource-acquisition-is-initialization (RAII). Since it looks like you are transitioning from C to C++, I recommend to read the tour of C++ which shows fundamental C++ principles. For RAII, the gist is to manage resources in special handler objects which allocate in the constructor and deallocate in the destructor, and implement move semantics. This way, you cannot leak resources. Example implementations are std::vector, std::unique_ptr or std::iostream. As another example, consider mutex locking/unlocking:

class Mutex {
public:
   void lock() { ... }
   void unlock() { ... }
};

When you use it, it easy to forget unlocking in your code, especially when making modifications to existing code. Also, in case of exceptions, you need try/catch blocks to unlock all the time. Instead, define a MutexLocker class:

class MutexLocker
{
public:
    MutexLocker(std::mullptr_t) = delete;
    MutexLocker(Mutex* m): mutex_(m) {mutex_->lock();}
    MutexLocker(MutexLocker const&) = delete;
    MutexLocker& operator=(MutexLocker const&) = delete;
    MutexLocker(MutexLocker&& l): mutex_(l.mutex_) {l.mutex_ = nullptr;}
    MutexLocker& operator=(MutexLocker&& l)
    {
        mutex_  = l.mutex_,
        l.mutex_ = nullptr;
        return *this;
    } 

    ~MutexLocker() {if (mutex_) {mutex_->unlock()} };
private:
    Mutex* mutex_;
};

Now, you can never forget to unlock a Mutex. The MutexLocker object cannot be copied, but you can transfer ownership. This is superior to anything you can do in C.

For formatting output, you can google "variadic template printf" which should give you some examples, e.g. on Wikipedia:

void printf(const char *s)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                throw std::runtime_error("invalid format string: missing arguments");
            }
        }
        std::cout << *s++;
    }
}

template<typename T, typename... Args>
void printf(const char *s, T value, Args... args)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                std::cout << value;
                s += 2; // this only works on 2 characters format strings ( %d, %f, etc ). Fails miserably with %5.4f
                printf(s, args...); // call even when *s == 0 to detect extra arguments
                return;
            }
        }
        std::cout << *s++;
    }    
}

Or you can use a library, e.g. boost::format or probably thousands of other implementations. If it is only for logging, you could take a look at a logging framework, e.g. boost.log.

Jens
  • 9,058
  • 2
  • 26
  • 43
  • Macro "FATAL" here is not the point. I just want to print variable amount of arguments, and format some of them in cases. – Galaxy Mar 05 '16 at 07:54
  • @hxpax See the second paragraph for an answer. – Jens Mar 05 '16 at 07:57
  • @hxpax It would also be good to adjust your example to what you really want because it is confusing. Strip it down to a minimal piece of code without FATAL etc. – Jens Mar 05 '16 at 08:02
  • Thank you, I've been using Log4Cplus for months, maybe I should not try Using C++ the C way. – Galaxy Mar 05 '16 at 08:06
  • this FATAL did a nice logging and extra action, that's why I put it here. – Galaxy Mar 05 '16 at 08:07
  • @hxpax The extra action is not needed if you program idiomatic C++. RAII and exceptions take care of resources. As a benefit, you will not have memory or other resources leaking. As a rule of thumb, wrap every resource in a RAII container, e.g. ban `new`/delete for std::vector or std::string. That will make your life much easier. – Jens Mar 05 '16 at 08:09
  • Here, CLEAN_UP wraps resource recycling. Try & catch is slow, I do not want to use them in low level programming. – Galaxy Mar 05 '16 at 08:23
  • @hxpax RAII is not about releasing resources in catch blocks. It is about deterministic resource management by wrapping resources in classes which release in their destructor. This way, in case an exception is thrown, it gets automatically released. And you have no leaks. `std::vector` is an example. I suggest you read up on it. And if you are concerned about performance in case an exception is thrown (there is not impact in the normal path), you should consider that it should only in exceptional cases. – Jens Mar 05 '16 at 08:44
  • @hxpax Depending on how low-level you are prgramming, http://www.amazon.com/Real-Time-Efficient-Object-Oriented-Microcontroller-Programming/dp/3642346871 is a good read. – Jens Mar 05 '16 at 09:01
  • @hxpax You may also be interested in http://www.embedded.com/design/programming-languages-and-tools/4438660/Modern-C--in-embedded-systems---Part-1--Myth-and-Reality?isCmsPreview=true and http://www.embedded.com/design/programming-languages-and-tools/4438679/3/Modern-C--embedded-systems---Part-2--Evaluating-C-- – Jens Mar 05 '16 at 09:07
  • Thank you @Jens , though your answer is not accepted to this question, but it's really imformative. – Galaxy Mar 05 '16 at 15:50
0

First, even it often leads to harder to maintain code, you call always use C techniques in C++. stdio.h functions work natively in C++ and almost all macro are translated the same.

If you want to make use of c++ goodies (better type control at compile time)... you will have to forget old C variadic functions, notably all xprintf. There may be one interesting part anyway with templates.

Anyway the example given in the referenced Q&A is all you need here. Formatting instructions are simply injected in streams the same values are.

But here is a C++11 example showing that you can do what you want without using any macro. It is much longer than the C macro version, but it looks form me much more clear and extensible without the ugly do { ... } while 0 idom:

#include <iostream>
#include <string>

// disp is a variadic templated function injecting any arguments to a stream
// version for one single arg
template <typename T>
void disp(std::ostream& out, T arg) {
    out << arg;
}

// recursively displays every arg
template <typename T, typename ... U>
void disp(std::ostream& out, T arg, U ... args) {
    disp(out, arg) ;
    disp(out, args...);
}

/* fatal displays its args to std::cout, preceded with "FATAL " and followed 
 * by a newline.
 * It then does some cleanup and exits
 */
template<typename ... T>
void fatal(T ... args) {
    std::cout << "FATAL ";
    disp(std::cout, args...);
    std::cout << std::endl;

    // cleanup
    exit(1);
}

int main() {
    int i = 21;
    int j = 32;
    std::string s = "foo";
    if(1) fatal(1, " " , s, " ab ", i, " 0x", std::hex, j);
    return 0;
}

output is

FATAL 1 foo ab 21 0x20

Last but not least, you'd better use a throw FatalException() where FatalException is a subclass of std::exception instead of directly use exit(1). You could even write to a stringstream and pass the resulting string to the exception instead of writing to a real stream, but then you should be prepared to deal with bad_alloc exceptions.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252