在 C++03 中模拟 lambda 以在宏中进行流控制

Emulating lambdas in C++03 for flow-control purposes in macros

提问人:Bwmat 提问时间:8/22/2023 最后编辑:Bwmat 更新时间:8/23/2023 访问量:98

问:

我在头文件中有一些现有代码,需要在 C++03 和 C++11 的上下文中使用

它定义了一个宏,该宏接受 -style 格式字符串和参数,并使用该字符串进行格式化,将结果输出到 ,然后调用TABORTprintfstdoutstd::abort

从本质上讲,类似于

#define TABORT(...) do { fprintf(stderr, "Aborting at %s:%d for reason: ", __FILE__, __LINE__); fprintf(stderr, __VA_ARGS__); std::abort(); } while(0)

我想添加一些逻辑来捕获评估会引发异常的情况,从而阻止对 .例如,在以下内容中:__VA_ARGS__std::abort

if (SomethingReallyBadHappened())
    TABORT("Aborting because %s", GetReallyBadDetails().c_str());

如果抛出异常,我想确保调用中止(在这里,而不是在一些异常展开之后)。GetReallyBadDetails

所以,我做了这样的事情:

#define TABORT(...) do { fprintf(stderr, "Aborting at %s:%d for reason: ", __FILE__, __LINE__); try { fprintf(stderr, __VA_ARGS__); } catch (...) { fprintf(stderr, "<Failed to evaluate abort message>\n"); } std::abort(); } while(0)

但是,当宏用于标记为 的函数时,这会导致 Visual Studio 中的 C4714 警告,可能是由于__forceinline

在某些情况下,编译器不会内联 机械原因。例如,编译器不会内联:

  • 带有 try(C++ 异常处理)语句的函数。

因此,为了避免该警告(并保留以前确定需要内联内联的内函数[我希望这是与分析一起完成的......]),我正在考虑做类似的事情

// In header

// Assume this is like std::function except construction from a lambda can't throw
template <typename Sig>
class function_ref;
using PrintAbortMsg = function_ref<void(const char*)>;
void [[noreturn]] do_tabort(const char* file, int line, function_ref<void(PrintAbortMsg &)> msgGenerator) noexcept;
#define TABORT(...) do_tabort(__FILE__, __LINE__, [&](PrintAbortMsg& print) { print(SprintfToStdString(__VA_ARGS__).c_str()); })

// In cpp

void do_tabort(const char* file, int line, function_ref<void(PrintAbortMsg&)> msgGenerator) noexcept
{
    fprintf(stderr, "Aborting at %s:%d for reason: ", __FILE__, __LINE__);
    try
    {
        msgGenerator([](const char* msg) { fprintf(stderr, "%s\n", msg); });
    }
    catch (...)
    {
        fprintf(stderr, "<Failed to evaluate abort message>\n");
    }
    std::abort();
}

我认为这在 C++11 的上下文中有效,但我不确定如何做一些适用于 C++03 的事情。我不想触及宏的现有用法。

C++ 可视化工作室 11 C++03

评论

0赞 Peter 8/22/2023
lambda 本质上是一种匿名类型,其成员表示(任何)捕获的数据,with 参数表示 lambda 函数的“主体”。在 C++11 之前,您可以通过定义这样的结构、创建一个实例并调用其 .结构类型和实例都需要唯一的名称(例如,通过使用类似的东西的令牌粘贴(源文件中的一行代码是唯一的,但不是源文件之间的唯一名称,只要您不使用某些预处理器支持的扩展)structoperator()operator()__LINE__#line
0赞 Bwmat 8/22/2023
@Peter - 这里的问题是,如何在与调用宏的函数分开的函数的上下文中透明地执行作为参数传递给宏的表达式?现在再想想,我认为如果没有 C++ lambda,通常可能是不可能的,因为这些表达式可以引用使用宏的本地范围内的某些内容
2赞 Peter 8/22/2023
@Bmwat 通过该评论,您刚刚表明您不了解 lambda 的工作原理。lambda 函数中的表达式不引用使用宏的本地范围内的内容。lambda 只是一个符号简写,用于创建在其构造过程中填充数据(或引用)的数据结构。该结构体访问结构体本身的成员 - 这些成员持有所谓的捕获变量的值或指针/引用。你把它当作某种无法模仿的魔法,你错过了重点。operator()
1赞 Igor Tandetnik 8/22/2023
你无法复制的编译器魔法隐藏在 lambda 前导码中,带有隐式捕获。编译器将查看 中的表达式,找出其中提到的变量,将这些变量捕获到 lambda 对象的数据成员中,然后在 中的这些数据成员上重新组合表达式。我不明白如何在没有 lambda 的宏中实现这一点 - 预处理器无法以这种方式对表达式进行 X 射线检查。[&]__VA_ARGS__operator()
1赞 Igor Tandetnik 8/22/2023
我看到你已经拒绝了宏将实例化一个调用其析构函数的类的答案。但是为什么?您声明的最终目标不是模拟 lambda(这只是达到目的的一种手段),而是确保即使在宏中计算的内容引发异常时也会被调用。示波器防护装置可以很好地做到这一点。那么,该解决方案在哪些方面无法满足您的要求呢?abort()abort()

答:

1赞 Igor Tandetnik 8/22/2023 #1

你工作太辛苦了。只需编写一个调用其析构函数的类即可。在宏中的左大括号之后创建该类的实例。它将被销毁并在右大括号处调用,无论该块是正常退出还是通过异常退出。如abort()abort

struct AbortWhenDestroyed {
  ~AbortWhenDestroyed() { abort(); }
};

#define TABORT(...) do { \
  AbortWhenDestroyed abort_me; \
  fprintf(stderr, "Aborting at %s:%d for reason: ", __FILE__, __LINE__); \
  fprintf(stderr, __VA_ARGS__); \
} while(0)

进一步研究的搜索词是“范围保护”。参见:std::experimental::scope_exit


您也可以使此类打印,如try/catch示例中所示。沿着这些思路:<Failed to evaluate abort message>

struct AbortWhenDestroyed {
  ~AbortWhenDestroyed() {
      if (!normal_exit) {
        fprintf(stderr, "<Failed to evaluate abort message>\n");
      }
      abort();
  }
  bool normal_exit = false;
};

#define TABORT(...) do { \
  AbortWhenDestroyed abort_me; \
  fprintf(stderr, "Aborting at %s:%d for reason: ", __FILE__, __LINE__); \
  fprintf(stderr, __VA_ARGS__); \
  abort_me.normal_exit = true;
} while(0)

如果 evaluate 引发异常,则不会执行该行,析构函数将在中止之前发出警告。__VA_ARGS__abort_me.normal_exit = true;