写在开篇
C++ 预处理器(宏)上篇–原理与基本用法中讲到了C/C++宏的各种语法以及语法细节,因此下面我就不会对语法细节进行赘述了。
其中我说到,个人认为宏的最重要作用是:它是一段说明文字,能让人一眼看出这段看出这段代码的作用。
下面我会带着大家看宏中常见的用法,这样大家也就可以理解这句话。
我将这里我根据使用的位置将宏分为两类:一种是头文件宏,一种是源文件宏。顾名思义,这两种宏分别被头文件和源文件引入。
题目例子
我先举一个例子:
有一个家庭,父亲叫老王,儿子叫小明,女儿叫小红,至于妈妈不好意思名字没想好,先跳过了。然后老王由一辆车车牌是“A888888",每天用来送小明和小红上学。
题目分析:家庭代表着namespace为Family,老王有车一辆。需要做的活动是送孩子上学。
下面就让我们写一段程序模拟这个过程。
源码
我们定义一个类LaoWang,这个类的命名空间是Family,老王他有一个属性是一辆车(这个车可以获取他的车牌,车坏了可能会换,我们用get/set方法来给外部提供接口),需要做的活动是送孩子上学。并且由于老王是一个独立的人,所以我们不允许对老王进行拷贝。
LaoWang.h
#pragma once
#include "HeadMarco.h"
#include <string>
#include <memory>
BEGIN_NAMESPACE(Family)
class LaoWang
{
DISABLE_CLASS_COPY(LaoWang);
DECLARE_CLASS_PROPERTY(std::string, Car, "A88888")
public:
LaoWang() = default;
~LaoWang() = default;
void send2School();
};
DECLARE_SHARED_PTR(LaoWang)
END_NAMESPACE
在上面我们使用了BEGIN_NAMESPACE
和END_NAMESPACE
,来进行命名空间的声明。此外,使用DISABLE_CLASS_COPY
宏禁止了拷贝构造和拷贝赋值,为了简写get/set函数使用了DECLARE_CLASS_PROPERTY
宏,来快速生成对应的代码,最后为了方便shared_ptr的使用通过DECLARE_SHARED_PTR
声明了一个shared_ptr
的别名。
LaoWang.cpp
#include "LaoWang.h"
#include "CppMarco.h"
BEGIN_NAMESPACE(Family)
DEFINE_CLASS_PROPERTY(LaoWang, std::string, Car)
void LaoWang::send2School() {
std::cout << "send kids to school" << std::endl;
}
END_NAMESPACE
在源文件中,使用DEFINE_CLASS_PROPERTY
来快速生成get/set函数的定义。
main.cpp
#include <iostream>
#include <thread>
#include <chrono>
#include "LaoWang.h"
int main() {
std::chrono::hours oneDay(24);
Family::LaoWangSharePtr lao_wang = \
std::make_shared<Family::LaoWang>();
while (true) {
lao_wang->send2School();
std::this_thread::sleep_for(oneDay);
}
return 0;
}
在main.cpp
中,通过一个无限循环和C++11中的chrono
和thread
库,让老王每天送孩子上学(send2School
)。不对,周末周日好像不用上学,算了不要在意这些细节,那就让老王送他们去上补习班吧。
虽然上面没把宏的具体写法写出来,应该能理解我前面说的,宏就是一段说明文件。看到BEGIN_NAMESPACE
就知道这里有个命名空间,看到DISABLE_COPY
,就知道该类不允许复制。不仅代码写的快了,更重要的是通过宏可以很快的看出这个类的一些功能,方便了代码的阅读。
头文件宏
namespace声明宏
#ifndef BEGIN_NAMESPACE
#define BEGIN_NAMESPACE(SPACE) namespace SPACE {
#endif
#ifndef END_NAMESPACE
#define END_NAMESPACE }
#endif
禁止类的拷贝构造以及拷贝赋值
#ifndef DISABLE_CLASS_COPY
#define DISABLE_CLASS_COPY(CLASS) \
private: \
CLASS(const CLASS&)=delete; \
CLASS& operator=(const CLASS&)=delete;
#endif
定义智能指针的别名
#ifndef DECLARE_SHARED_PT
#define DECLARE_SHARED_PTR(CLASS) \
using CLASS##SharedPtr = std::shared_ptr<CLASS>;
#endif
类中属性声明宏
#ifndef DECLARE_CLASS_PROPERTY
#define DECLARE_CLASS_PROPERTY(TYPE, NAME, DEFAULT_VALUE) \
private: \
TYPE m_##NAME = DEFAULT_VALUE; \
public: \
const TYPE& get##NAME() const; \
void set##NAME(const TYPE & value);
#endif
这里有个细节,通常同文件宏,需要定义一个“逗号”宏。这里是一个容易遇到的小坑
#ifndef COMMA
#define COMMA ,
为啥需要这样一个逗号宏呢,主要是因为宏是通过逗号就行分割的,但是模板类型中也有额外的逗号;比如说想声明一个类型为std::unordered_map<std::string, int>
的属性
// 错误写法
DECLARE_CLASS_PROPERTY(std::unordered_map<std::string, int>,
ClassMap, {})
// 正确写法,用COMMA宏代替逗号
DECLARE_CLASS_PROPERTY(std::unordered_map<std::string COMMA int>,
ClassMap, {})
源文件宏
类中属性定义宏
#ifndef DEFINE_CLASS_PROPERTY
#define DEFINE_CLASS_PROPERTY(CLASS, TYPE, NAME) \
const TYPE& CLASS##::get##NAME() const { \
return m_##NAME; \
} \
void CLASS##::set##NAME(const TYPE & value) { \
m_##NAME = value; \
}
#endif // !PROPERTY(CLASS, NAME, DEFAULT_VALUE)
通常的话我们就使用if语句来实现就行,但是如果代码中if语句太多了,看起来会非常的累,并且会让人感觉是代码中充满了“异味”。
因此我经常使用宏来替换简短的if语句
在上面的例子中补充一点,假定小明生病了,我们就不需要送他去上学。
void LaoWang::send2School() {
...
CHECK_EXPRESS(xiao_ming.is_sick(),
std::cout << "don't send xiao ming to school" << std::endl)
...
}
按照上面的思路,我补充一些常用的宏,给大家提供思路
#ifndef CHECK_EXPRESS
#define CHECK_EXPRESS(condition, express) \
if (condition) { \
express; \
}
#endif
#ifndef CHECK_RETURN_VOID
#define CHECK_RETURN_VOID(condition) \
if (condition) { \
return ; \
}
#endif
#ifndef CHECK_RETURN_VALUE
#define CHECK_RETURN_VALUE(condition, value) \
if (condition) { \
return value; \
}
#endif
#ifndef CHECK_RETURN_VOID_ASSERT_LOG
#define CHECK_RETURN_VOID_ASSERT_LOG(condition) \
if (condition) { \
printf(#condition); \
assert(0); \
return ; \
}
#endif
上面的这些宏主要是在满足一定条件后,执行一些语句、断言返回等等功能,注意是给大家提供点思路,还可以有很多常用宏CHECK_BREAK
、CHECK_CONTINUE
、ASSERT_LOG
等等
总结
宏有优点有缺点,宏的本质是在预处理阶段进行复杂的文本替换, 因此具有高度的灵活性,可以实现代码的通用性和重用性。宏的很多缺点,比如宏展开的问题,在现在IDE功能越来越完善的今天,借助IDE也可以很方便的展开。在C/C++中利用宏的灵活性,可以让代码更加的简洁,可读性也更加的高。
要是大家觉得写的还可以有所帮助的话,可以点点赞,收收藏。公众号同名,感兴趣可以关注一下,拜谢!