我曾经使用可变数量的参数和格式来登录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 ++中。
答案 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;
此行将打印出该消息并抛出异常。
您可以根据需要自定义此项。写入日志文件,添加时间戳或其他有用信息,详细级别和阈值。甚至可以将大多数案例在生产构建中完全优化。
答案 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::vector
,std::unique_ptr
或std::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()
FatalException
,std::exception
是[{1}}的子类,而不是直接使用exit(1)
。您甚至可以写入stringstream
并将结果字符串传递给异常而不是写入实际流,但是您应该准备好处理bad_alloc
例外。