有没有办法将以下内容编写为C++宏?

20
my_macro << 1 << "hello world" << blah->getValue() << std::endl;

应该扩展为:

std::ostringstream oss;
oss << 1 << "hello world" << blah->getValue() << std::endl;
ThreadSafeLogging(oss.str());

我想知道你是否能够执行 #define my_macro(blah) { std::ostringstream oss; oss blah; ThreadSafeLogging(oss.str()); }。 - Nick Bedford
参见:https://dev59.com/llLTa4cB1Zd3GeqPcavh - André Kugland
7个回答

76
#define my_macro my_stream()
class my_stream: public std::ostringstream  {
public:
    my_stream() {}
    ~my_stream() {
        ThreadSafeLogging(this->str());
    }
};
int main() {
    my_macro << 1 << "hello world" << std::endl;
}
创建了一个临时的my_stream类型对象,它是ostringstream的子类。对该临时对象的所有操作都像在ostringstream上进行的一样。
当语句结束时(即在主函数中整个打印操作的分号右边),临时对象超出了范围并被销毁。 my_stream 析构函数通过之前“收集”的数据调用 ThreadSafeLogging
已经通过(g++)测试。
感谢/dingo指出如何简化整个过程,使我不需要重载operator<<。可惜赞不能分享。

8
这是我见过的对 C++ 析构函数最巧妙(或滥用)的运用。 - anon
2
查看这个关于换行符问题的问题。https://dev59.com/XnNA5IYBdhLWcg3wAItP - Jasper Bekkers
12
将std::ostringstream作为my_stream的基类是否更简单?这样看来,只需要析构函数。 - Dingo
4
除此之外,这真的很棒。我会放弃使用宏并直接使用my_stream(),+1。 - kikito
1
你怎么可能给一个真正糟糕、不可行且可能引起问题的C++小技巧投这么多票呢?(尝试使用my_macro << std::string( "surprise " ))在C++中,流不应被用作临时变量。创建<<>>运算符的成本并不高。 - Nikko
显示剩余7条评论

3
您是否可以从ostream派生并提供自己的线程安全实现呢?这样您就可以执行以下操作:
myCOutObject << 1 << "hello world" << blah->getValue() << std::endl;

你是否想不使用宏定义,正确使用C++来获得完全相同的功能?


2
考虑到您的代码中已包含这些行,是可以实现的。
#include <iostream>
#include <sstream> 

__LINE__宏由所有标准编译器定义。因此,我们可以使用它来生成每次使用该宏时都不同的变量名 :)

这是一个被视为单语句指令的新版本: (已编辑)

#define Var_(Name, Index) Name##Index
#define Var(Name, Index) Var_(Name, Index)
#define my_macro \
for (struct { int x; std::ostringstream oss; } Var(s, __LINE__) = { 0 }; \
     Var(s, __LINE__).x<2; ++Var(s, __LINE__).x)  \
    if (Var(s, __LINE__).x==1) ThreadSafeLogging(Var(s, __LINE__).oss.str()); \
    else Var(s, __LINE__).oss

// So you can use it like this 
int main() 
{ 
    if (4 != 2)
        my_macro << 4 << " hello "  << std::endl; 
    my_macro << 2 << " world !" << std::endl; 
} 

开发人员可能不需要在同一行上两次使用此宏,因为运算符<<的简单性。但是,如果您需要这样做,可以通过__COUNTER__(非标准)切换使用__LINE__。感谢Quuxplusone提供了这个提示。


为什么要给它命名?你可以使用一个对象 MyObject() 作为临时变量,并依赖于在语句结束时它被销毁。 - dascandy
聪明!请注意,在所有流行的编译器上,您可以使用非标准的__COUNTER__而不是标准的__LINE__,然后即使在同一行上多次使用它也可以工作。但是,在任何情况下,这个宏都不是卫生的;如果您说if (log) my_macro << 4 << std::endl;,那么您会遇到麻烦。基于google-glog的LOG()析构函数技巧的最高投票答案是卫生的。 - Quuxplusone
@Quuxplusone:你说得对,你所问的确实是编写宏时“标准”的一种。因此,我更新了解决方案。 - Astyan
我认为for技巧足以使您的宏具有卫生性,但这相当微妙,我不确定。(啊,我看到Nicolas在下面提供了一个更简洁的例子。)简化当前宏的一种方法是将重复的子表达式Var(s, __LINE__)分解为一个变量:#define my_macro my_macro_helper(Var(s, __LINE__)),然后#define my_macro_helper(v) for... - Quuxplusone

2

不行。问题在于,如果没有使用函数语法,宏只能在其所在位置被替换。

但是,如果您愿意使用函数语法,那么您就可以在参数之前和之后替换内容。

my_macro(1 << "hello world" << blah->getValue() << std::endl);

您可以通过下面的方式定义MyMacro:

MyMacro定义如下:

#define my_macro(args) std::ostreamstring oss; \
                       oss << args; \
                       ThreadSafeLogging(oss.str());

2
看一下 google-glog,他们使用一个临时对象来实现这个。
LOG(INFO) << "log whatever" << 1;

他们还有其他有趣的宏,比如LOG_IF等。


1

我有的日志设置非常相似:

bool ShouldLog(const char* file, size_t line, Priority prio);

class LoggerOutput : public std::stringstream {
public:
  LoggerOutput(const char* file, size_t line, Priority prio) 
  : prio(prio) 
  {
    Prefix(file, line, prio);
  }
  void Prefix(const char* file, size_t line, Priority prio);
  ~LoggerOutput() {
    Flush();
  }
  void Flush();
private:
  Priority prio;
};

#define LOG(Prio) if (!Logging::ShouldLog(__FILE__, __LINE__, Prio)) {} else Logging::LoggerOutput(__FILE__, __LINE__, Prio)

如果您的日志记录被禁用,则不会创建 ostream,因此几乎没有开销。您可以根据文件名和行号或优先级级别配置日志记录。ShouldLog 函数在调用之间可能会更改,因此您可以节流或限制输出。日志输出使用两个函数来修改自身:Prefix 函数将 "file:line: (PRIO)" 前缀添加到行中,而 Flush() 函数则将其作为单个命令刷新到日志输出并添加一个换行符。在我的实现中,它总是这样做,但如果已经有条件,则可以使其成为有条件。


1
这是我在其他地方看到的另一个恶劣技巧。与我的另一个答案相比,它具有显着的缺点:你不能在同一作用域中使用它两次,因为它声明了一个变量。然而,在其他情况下仍可能很有趣,比如你想让 somemacro foo 在执行 foo 后运行某些东西。
#define my_macro \
    std::ostringstream oss; \
    for (int x=0; x<2; ++x) \
        if (x==1) ThreadSafeLogging(oss.str()); \
        else oss

int main() {
    my_macro << 1 << "hello world" << std::endl;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接