C ++可变数量的参数与std :: cout的格式

时间:2016-03-05 07:07:48

标签: c++ c++11

我曾经使用可变数量的参数和格式来登录C,我想如何在C ++中实现这一点。

通过这样的Q& A(How to make a variadic macro for std::cout?),我知道如何处理变量金额。但我仍然不知道的是,如何格式化,因为我不能使用像#set; setbase'这样的方法。在现在的论点之间。

例如:

// 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;
}

"致命"在这里,接受带有格式的可变数量的参数,打印它们,并做一些额外的操作。我不知道如何宣布这样一个"致命"在C ++中。

4 个答案:

答案 0 :(得分:2)

您可以通过在临时日志记录对象上使用operator<<和自定义析构函数来实现此目的。

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);
}

我只包含了基本用法的基本要素。对于更复杂的用法,您需要考虑ctor / operator<<

的副本变体

用法非常惯用C ​​++。但你必须记住()

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

此行将打印出该消息并抛出异常。

您可以根据需要自定义此项。写入日志文件,添加时间戳或其他有用信息,详细级别和阈值。甚至可以将大多数案例在生产构建中完全优化。

Live example

答案 1 :(得分:1)

C ++不是C!虽然您可以使用C风格(通常是C代码),但这是不可取的。首先,您通常不应该依赖宏,因为它们违反了类型系统,而是使用(可能是内联或constexpr)函数。那么你不应该使用C风格的错误处理技术,而是使用异常。我也建议一般反对可变参数,最后你不需要C风格的字符串格式化技术 - &gt;这是C ++,使用stringstreams来格式化你的代码。

在您的特定情况下,我会做这样的事情:

#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;
}

答案 2 :(得分:1)

我还必须指出,C ++和C是两种不同的语言,具有不同的模式和习语。对于许多类型安全的C构造,C ++有更好的替代方案,因此更受欢迎。在你的情况下,我会在这种情况下抛出异常。如果您在代码中禁用catch(...),它将终止您的程序。传播异常时,编译器还将调用对象的析构函数,从而进行清理。如果你还没有,我建议你阅读资源获取初始化(RAII)。由于看起来您正在从C转换到C ++,我建议您阅读the tour of C++,其中显示了基本的C ++原则。对于RAII,要点是管理特殊处理程序对象中的资源,这些对象在构造函数中分配并在析构函数中释放,并实现移动语义。这样,您就无法泄漏资源。示例实现是std::vectorstd::unique_ptrstd::iostream。作为另一个例子,考虑互斥锁定/解锁:

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

使用它时,很容易忘记代码中的解锁,尤其是在修改现有代码时。此外,如果出现异常,您需要尝试使用try / catch块来解锁。相反,定义一个MutexLocker类:

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_;
};

现在,您永远不会忘记解锁Mutex。无法复制MutexLocker对象,但您可以转移所有权。这比你在C中可以做的任何事都要好。

对于格式化输出,您可以谷歌&#34; variadic template printf&#34;这应该给你一些例子,例如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++;
    }    
}

或者您可以使用图书馆,例如boost::format或者可能有数千个其他实现。如果仅用于日志记录,您可以查看日志记录框架,例如boost.log

答案 3 :(得分:0)

首先,即使它经常导致更难维护代码,你也总是在C ++中使用C技术。 stdio.h函数在C ++中本机工作,几乎所有的宏都是相同的。

如果你想利用c ++好东西(在编译时更好的类型控制)......你将不得不忘记旧的C变量函数,特别是所有xprintf。无论如何,模板可能有一个有趣的部分。

无论如何,参考Q&amp; A中给出的例子就是你所需要的。格式化指令只是在相同值的流中注入。

但这是一个C ++ 11示例,显示您可以在不使用任何宏的情况下执行所需操作。它比C宏版本长得多,但它看起来更加清晰和可扩展,没有丑陋的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;
}

输出

FATAL 1 foo ab 21 0x20

最后但同样重要的是,您最好使用throw FatalException() FatalExceptionstd::exception是[{1}}的子类,而不是直接使用exit(1)。您甚至可以写入stringstream并将结果字符串传递给异常而不是写入实际流,但是您应该准备好处理bad_alloc例外。