C++中宏的常见使用

1 阅读2分钟

摘要

本文全面介绍 C++ 宏(Macro)的使用方法,从基础概念到高级技巧。主要内容包括:

  1. 基础用法:简单替换、多行宏(使用 \ 续行)、嵌套宏(宏调用宏)
  2. 运算符技巧# 运算符将参数转为字符串,## 运算符拼接标识符
  3. 高级特性:可变参数宏(Variadic Macros)处理不定数量参数
  4. 实际应用:通过 BusTub 项目案例展示宏在代码生成、错误处理、日志记录中的实用技巧
  5. 原理与局限:宏本质是编译前的文本替换,因此不遵守作用域、无法调试、缺乏类型检查
  6. 最佳实践:优先使用函数、参数加括号、注意分号使用

宏作为"代码生成器",能有效减少样板代码,但需谨慎使用以避免常见陷阱。

1. 最基础 — 简单替换

#define PI 3.14
#define BUSYUB_ASSERT(expr, msg) assert((expr) && (msg))

2. 多行宏 — 用 \ 续行

宏定义可以跨多行,每行末尾加 \

#define BUSTUB_ENSURE(expr, message)                  \
  if (!(expr)) {                                      \
    std::cerr << "ERROR: " << (message) << std::endl; \
    std::terminate();                                 \
  }

3. 嵌套宏 — 宏调用宏

DISALLOW_COPY_AND_MOVE 就是嵌套宏:

#define DISALLOW_COPY_AND_MOVE(cname) \
  DISALLOW_COPY(cname);               \
  DISALLOW_MOVE(cname);

外层宏先展开参数 cname,再调用内层宏。这能减少重复代码。

4. # 运算符 — 把参数变成字符串

#define ASSERT_MSG(cond) #cond

ASSERT_MSG(x > 0);  // 展开成 "x > 0"

应用:自动生成带变量名的报错。

5. ## 运算符 — 拼接标识符

#define MAKE_GETTER(name) int get_##name() { return name##_; }

struct Foo {
  int age_;
  MAKE_GETTER(age)  // 自动生成 get_age() 函数
};

应用:自动生成重复样板代码。

6. 可变参数宏(Variadic Macros)

#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)

LOG("name=%s, age=%d\n", "tom", 18);

... 接收可变参数,__VA_ARGS__ 代表所有参数。

7. 为什么宏能做到这些?

宏本质是==编译前的纯文本替换==(发生在预处理阶段,比编译还早)。所以它可以:

  • 出现在类、函数能出现的任何地方(包括类内部的 public: 段)
  • 生成任何合法 C++ 代码
  • 重复使用同一个名字多次,每次展开不同

8. 宏的缺点(也很重要)

  1. 不遵守作用域 — 全局生效
  2. 不调试 — 断点打不到宏里
  3. 容易出错 — 优先级问题:#define SQUARE(x) x*xSQUARE(1+2) 得 5,不是 9
  4. 不类型检查 — 编译器看到的是展开后的代码

9. 最佳实践

  • 函数能做的事别用宏
  • 宏的每个参数用括号包起来#define SQR(x) ((x)*(x))
  • 多行宏结尾别漏分号(或者在调用处加分号)

简单说,宏就是“代码生成器”,可以用它来减少样板代码。理解了 DISALLOW_COPY_AND_MOVE 的展开过程,其他宏套路都差不多。