【问题标题】:C(++): Replace function declaration with macros but not its invocationsC(++):用宏而不是它的调用替换函数声明
【发布时间】:2021-10-19 19:18:18
【问题描述】:

我有一个库,我无法更改在单个文件中声明、实现和使用某些函数的位置。许多其他的东西都在同一个文件中完成。我想覆盖那个单一的功能来做另一件事。问题是,当我使用重命名宏时,找到该宏/函数名称的所有位置都会被替换,包括声明和调用。我想要一些方法来重命名声明或调用。然后我就可以简单地将我自己的函数放在初始名称下作为替换。

代码如下所示:

// library-module.cpp

int foo(int a, int b)  // declaration
{
    return a+b;
}

int bar()
{
    return foo(1, 2);  // invocation
}


// main.cpp

// MAGIC HAPPENS HERE
#include "library-module.cpp"

int foo(int a, int b) // drop-in replacement
{
    return a-b;
}

int main()
{
    return bar(); // should be -1, not 3
}

另一种选择是进行反之亦然的操作:将所有调用替换为某个其他符号,但不替换声明。虽然它不是首选,因为可能还有其他用户使用该函数名称。

另一种选择是在运行时执行此操作,但我相信它不是那么好。

谢谢。

UPD1

它是 c++。

这是文件中的实际函数declarationimplementationusage

我想要的是从使用该应用程序所有代码的其他应用程序中替换函数set_scenario 的单行,而不涉及任何其他内容。这个想法是保留所有代码的维护,除了那个非常功能,核心团队,并自己处理这个,因为我需要一些特定的实现。因此,我不包括标题,而是实现,并且想要这样一个肮脏的黑客。也许我走错路了?

最后我做了一个分叉,改变了线路并解决了这个问题,虽然我相信如果我不做一个分叉会更好,因为获得改变变得更加困难。

我想到的另一个想法可能是我必须为要更改的功能制作补丁并将其应用于每个构建。这样就可以准确地知道那一行发生了什么变化,并且如果我有补丁应用错误,我会立即知道初始函数发生了变化。也许这是实现相同目标的更好方法?

最后,使用宏来处理这个问题对我来说非常有趣,因为这对我来说是一种黑魔法。

再次感谢您。

【问题讨论】:

  • C 还是 C++?你能展示一下exact函数的声明和用法吗?细节可能很重要。
  • 你可以用#undef MACRO_NAME禁用宏,这样行吗?
  • 您通常不包含.cpp 文件,只包含.h 文件。
  • 定义宏你定义了它替换的函数之后。
  • 如果 OP 使用 C++,在自定义命名空间中定义他们希望覆盖的函数是一个好的解决方案吗?

标签: c++ macros


【解决方案1】:

这可能会也可能不会,取决于函数的定义和使用方式。

假设您显示的确切声明和用法,您可以这样做:

#define foo(a, b) FOO_LOW(CAT(DETECT_, a) CAT(DETECT_, b))(a, b)

#define CAT(a, b) CAT_(a, b)
#define CAT_(a, b) a##b

#define DETECT_int ,

#define FOO_LOW(...) FOO_LOW_(__VA_ARGS__)
#define FOO_LOW_(a, ...) CAT(FOO_IMPL, __VA_OPT__(_DECL))

#define FOO_IMPL(a, b) replacement(a, b)
#define FOO_IMPL_DECL(a, b) foo(a, b)


constexpr int foo(int a, int b) {return a + b;}
constexpr int replacement(int a, int b) {return a - b;}

static_assert(foo(100, 10) == 90);

此实现需要 C++20,另外 MSVC 需要 /Zc:preprocessor 和 Clang(13 及更早版本,请参阅 bug)需要 -Wno-gnu-zero-variadic-macro-arguments。可以使用较早的标准而不使用这些标志来实现,但宏会更加复杂。

这个具体实现有一些限制:

  • 它会检查两个参数是否都以int 开头,所以如果它发生在函数调用中,它将被错误地视为声明,例如foo(int(1), int(2))
  • 如果参数包含 , 之外的 ( ),则会出现编译错误,例如foo(std::array{1,2}[0], 1)
  • 如果参数以标点符号开头,则会出现编译错误,例如foo((1), (2))

【讨论】:

  • 令人印象深刻的预处理器-foo,+1。以及一个足够复杂和特定情况的结果(即使 使用 使用非常新的、C++ 特定/非标准功能),我很满意对于大多数人来说,对于大多数意图和目的,我的“你不能”在实践中是正确的。 “你不应该”在其他地方几乎都是真的。
  • 这太棒了,你让我对宏观魔法的理解有了一个全新的水平。现在对我来说不是那么黑了,谢谢!
【解决方案2】:

问题是,当我使用重命名宏时,找到该宏/函数名称的所有位置都会被替换,包括声明和调用。

是的。

我想要一些方法来重命名声明或调用。

预处理器不懂 C 或 C++ 语言。它知道如何识别标识符,但它不了解它们的 C 或 C++ 上下文,以便将声明与其他用途区分开来。因此,如果您有一个无法修改的代码块,那么您就不能使用预处理器选择性地仅替换该块中给定标识符的某些外观,而不是其他。

即使您可以修改相关代码,也可以在同一范围内多次声明多种标识符,只要它们不定义一次以上。因此,尽管原则上您可以通过代码并在所有需要将给定标识符在所有声明性上下文中识别为宏名称所需的位置插入 #define#undef 指令,但这在一般情况下并不容易而不是直接修改声明。

总的来说,从一开始这听起来就是个坏主意。不过,目前尚不清楚该推荐什么作为替代方案,因为该问题没有提供有关更高级别目标的详细信息,也没有提供有关建议的方法为何听起来很有吸引力的细节。

【讨论】:

  • 有可能,这取决于函数的使用方式。例如。您可以检查两个参数是否以int 开头,然后将其视为声明。但如果有人这样做foo(int(1), int(2))...
  • @HolyBlackCat,我邀请您解释如何使用预处理器实现它。
  • 发表了答案。
猜你喜欢
  • 2019-01-23
  • 1970-01-01
  • 2016-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多