C++中宏的优势相对于内联函数是什么?

8
我们知道,在C++中,宏定义相较于内联函数更为常见,因为编译器可以检查宏定义,并且在将同一操作(如 ++x)作为参数传递时不会重复计算,而内联函数则没有这个优势。
但是在面试中,有人问到宏定义在什么情况下比内联函数更为优越或更加适用于C++。
有没有人知道答案或对这个问题有想法?
9个回答

15

我所能想到的唯一一件事是,有些宏定义技巧无法通过内联函数实现。这包括在编译时将标记粘贴在一起等技巧。


12
自动获取文件名和行号是只有宏才能实现的另一个有用技巧。 - Ben Voigt

9

在某些特定情况下,宏不仅是首选,实际上也是唯一的方法来完成某些任务。

如果您想编写一个记录函数,该函数不仅记录某些消息,还要记录发生事件的文件和行号,则可以直接调用该函数,并直接输入文件和行值(或宏):

LogError("Something Bad!", __FILE__, __LINE__);

如果您希望它自动工作,您必须依赖于宏(警告:未编译):

#define LogErrorEx(ERR) (LogError(ERR, __FILE__, __LINE__))
// ...
LogErrorEx("Something Else Bad!");

在C++中,这不能通过模板、默认参数、默认构造或任何其他方式实现。


记录日志时,通常会有一个 if 语句来检查是否启用了特定级别的日志记录。当为假时,不会对记录日志的参数进行评估。 - edA-qa mort-ora-y

4
有时,您希望以其他任何方法都无法实现的方式扩展语言。
#include <iostream>

#define CHECK(x) if (x); else std::cerr << "CHECK(" #x ") failed!" << std::endl

int main() {
    int x = 053;
    CHECK(x == 42);
    return 0;
}

这将打印出CHECK(x == 42) failed!


不,它在return 0这一行上会出现编译错误——缺少分号。而且可能会有一个警告,指出else控制了一个空语句。 - Ben Voigt
@Ben:已修复!在我的GCC中没有任何警告。 - ephemient
如果你想让你的宏定义后面跟着一个分号,使用 do { ... } while (0)。你当前的宏定义允许像 CHECK(0) << 5; 这样的东西。 - Ben Voigt
我并不认为这是一件坏事;它允许在 CHECK 后面添加更多的诊断信息(例如:CHECK(foo) << "this is @j_random_hacker's fault" << std::endl;)。 - ephemient

1
在C++中,MACRO的一个常见用法(除了带有文件和行号的调试打印之外)是使用MACRO来填充一组无法从基类继承的类中的标准方法。在某些创建自定义RTTI、序列化、表达式模板等机制的库中,它们通常依赖于一组静态常量变量和静态方法(以及可能无法继承的某些重载运算符的特殊语义),这些方法几乎总是相同的,但需要添加到用户在该框架内定义的任何类中。在这些情况下,通常提供MACRO,以便用户不必担心放置所有必要的代码(他只需使用所需信息调用MACRO)。例如,如果我制作一个简单的运行时类型识别(RTTI)系统,我可能要求所有类都具有TypeID并且可以动态转换:
class Foo : public Bar {
  MY_RTTI_REGISTER_CLASS(Foo, Bar, 0xBAADF00D)
};   

#define MY_RTTI_REGISTER_CLASS(CLASSNAME,BASECLASS,UNIQUEID) \
  public:\
    static const int TypeID = UNIQUEID;\
    virtual void* CastTo(int aTypeID) {\
      if(aTypeID == TypeID)\
        return this;\
      else\
        return BASECLASS::CastTo(aTypeID);\
    };

以上内容无法使用模板或继承完成,它使用户的生活更轻松,避免了代码重复。

我认为这种宏的使用方式在C++中是最常见的。


1

正如之前提到的那样,宏可以使用预处理指令:__FILE____LINE__等。当然,#include#define也是有用的,可以用来参数化行为。

#ifdef __DEBUG__
#    define LOG(s) std:cout << s << std:endl
#else
#    define LOG(s)
#endif

根据 __DEBUG__ 是否被定义(通过 #define 或编译器选项),LOG 宏将会激活或不激活。这是一种简单的方式,在代码中随处添加调试信息并可以轻松地停用。

您还可以考虑更改内存分配方式(例如,将 malloc 重新定义为针对内存池而不是标准堆等)。


这段程序相关的内容如何:#ifdef __DEBUG__ -- inline void log() { ... } -- #else -- inline void log() {} -- #endif - Sebastian Mach

1

内联函数,顾名思义,仅限于函数任务,执行一些代码。

宏有更广泛的应用,例如可以扩展到声明或替换整个语言结构。以下是一些(针对 C 和 C++ 编写的)无法使用函数完成的示例:

typedef struct POD { double a; unsigned b } POD;
#declare POD_INITIALIZER { 1.0, 37u }

POD myPOD = POD_INITIALIZER;

#define DIFFICULT_CASE(X) case (X)+2 :; case (X)+3
#define EASY_CASE(X) case (X)+4 :; case (X)+5

switch (a) {
   default: ++a; break;
   EASY_CASE('0'): --a; break;
   DIFFICULT_CASE('A'): a = helperfunction(a); break;
}

#define PRINT_VALUE(X)                        \
do {                                          \
 char const* _form = #X " has value 0x%lx\n"; \
 fprintf(stderr, _form, (unsigned long)(X));  \
} while (false)

在C++的语境下,Boost有更多更复杂、更实用的示例。

但是,由于这些宏实际上是扩展了语言(虽然预处理器也是语言的一部分),许多人都不喜欢使用宏,特别是C++社区中,而在C社区中则稍微好一些。

无论如何,如果您使用这样的结构,您应该始终非常清楚地知道它们的目的,进行充分的文档记录,并抵制混淆代码的诱惑。


0
    #include <stdio.h>
    #define sq(x) x*x
    int main()
    {  
        printf("%d", sq(2+1));
        printf("%d", sq(2+5));
        return 0;
    }

这段代码的输出结果是5和17。宏展开是文本展开,不像函数那样。
这个例子的解释:
sq(2+1) = 2+1*2+1 = 2+2+1 = 5
sq(2+5) = 2+5*2+5 = 2+10+5 = 17

1
那么这就是宏的优势? - Sebastian Mach

0

宏定义就像文本替换一样。

我能想到的基本区别有:

  • 它不一定像函数一样具有函数形式。我的意思是,它不必包含一些一致集合的括号。
  • 它可以在其他地方使用。比如在类声明作用域中或者甚至在全局范围内。因此,它不能在另一个函数的作用域中。

如果要执行无法使用函数执行的操作,则必须使用宏定义:

  • 初始化复杂的表格(使核心更易读)
  • 简化某些特殊成员的声明,如事件 ID 或标记类(在 MFC IMPLEMENT_DYNAMIC 中经常使用)
  • 压缩函数开头重复的声明
  • 如上面提到的用于日志记录的使用__LINE____FILE__,...

-1

我会添加两个用途:

  1. MINMAX,直到 C++0x,因为返回类型必须手动声明,混合使用 minmax 作为内联函数将是一场噩梦,而一个简单的宏可以在眨眼间完成。
  2. 隐私:您始终可以在退出头文件之前取消定义宏,但无法“未声明”内联函数(或另一个符号)。这是由于 C 和 C++ 语言中缺乏适当的模块化所致。

现在,std::minstd::max作为模板函数可以很好地工作。 - edA-qa mort-ora-y
@edA-qa:不,它们不需要。它们要求两个参数都是相同类型。试图创建一个可以处理不同类型参数的std::min版本(在C++0x之前)是一种沮丧的练习(如果我记得正确,需要大约一百行代码),而使用宏,编译器可以轻松地处理整数提升。 - Matthieu M.
你说得对,它不会进行促销。实际上我认为这在这里是一件好事,但是嘿,你可能需要其他东西。如果缺少decltype,你就无法创建宏的内联版本。 - edA-qa mort-ora-y

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