C++预处理器(宏)下篇 -- 项目开发中的常见用法

写在开篇

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_NAMESPACEEND_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中的chronothread库,让老王每天送孩子上学(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_BREAKCHECK_CONTINUEASSERT_LOG等等

总结

宏有优点有缺点,宏的本质是在预处理阶段进行复杂的文本替换, 因此具有高度的灵活性,可以实现代码的通用性和重用性。宏的很多缺点,比如宏展开的问题,在现在IDE功能越来越完善的今天,借助IDE也可以很方便的展开。在C/C++中利用宏的灵活性,可以让代码更加的简洁,可读性也更加的高。

要是大家觉得写的还可以有所帮助的话,可以点点赞,收收藏。公众号同名,感兴趣可以关注一下,拜谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值