C/C++宏编程
宏的复杂使用,永远不要写两次!
介绍
我读过的所有C/C++教科书都批评宏的使用。“不要使用它们,它们很危险,因为它们隐藏了你实际写的东西。尤其是看起来很实用的宏。有些人甚至说,没有理由在C++的模板类的发明中使用宏。
尽管如此,宏仍然在某些地方使用。
例如,调试宏,如 、 等。它们都是看起来函数的宏,在调试和发布版本下扩展到不同的东西。ASSERTVERIFYTRACE
我知道有些人从不使用这些宏。相反,它们只是使用在调试和发布版本中以不同方式实现的函数。为什么?因为他们害怕使用看起来很函数的宏的威胁。也就是说,他们希望始终计算内部表达式,即使在不使用其值的发布版本中也是如此。好吧,这对某些人来说可能是合理的。事实上,将一些重要的代码放在里面(而不是)是一个非常常见的错误。ASSERTASSERTVERIFY
我个人总是使用这些的宏版本。那是因为我非常频繁地使用调试宏来在出现问题时立即获得指示,而另一方面,我不希望最终的可执行文件附带所有这些废话。
另一个广泛使用的宏的例子与ANSI/Unicode有关。这包括 ()、所有类似宏的宏。此外,Windows API 具有许多带有 或 后缀的函数,例如 、.也就是说,实际上是一个宏,它根据构建设置扩展到其中一个宏。_T_tprintfAWGetMessageAGetMessageWGetMessage
尽管受到批评,但宏仍在使用。有人可能会争论这是否合理,但这不是本文的主题。我想在本文中展示通过复杂使用宏可以完成的非常令人惊奇的事情。是否使用这些技术 - 决定取决于您。
通信协议示例
假设您必须实现一些通信协议。该协议由不同类型的“消息”组成,每条消息都有其特定的参数。
让我们同意(现在)每个传输的消息都从其 4 字节大小开始(从而将最大的消息限制为 4GB 的数量级),然后是它的 2 字节代码,然后是它的所有参数,这些参数是消息相关的。序号类型 (、、、、...) 按原样传输(不进行大端/小端转换)。字符串以 Unicode 字符集 (UTF16) 传输,前面的字符以字符为单位指定字符串的长度。ULONGUCHARUSHORTdoubleULONG
首先,我们需要以下消息类型:
-
登录。包含客户端版本 ()、用户名(字符串)、密码(字符串)。ULONG
-
登录结果。包含登录结果代码 ()。0=正常,1=凭据无效,依此类推。UCHAR
-
聊天消息。包含收件人用户名(字符串)、聊天文本(字符串)、一些额外的代码 ()。ULONG
那么,我们如何实现这一点呢?对于每种消息类型,我们需要以下内容:
- 消息类/结构的声明。
- 将此消息写入流(套接字/文件/等)的代码。
- 分析流中的消息的代码。
- 处理传入消息的代码。
出于此示例的目的,我们将使用以下抽象类进行流式处理:
C++
收缩 ▲
struct OutStream {
virtual void Write(LPCVOID pPtr, size_t nSize) = 0;
// ordinal types template <class T>
void Write_T(const T& val)
{
Write(&val, sizeof(val));
}
// variable-sized strings void Write_Str(const CString& val)
{
ULONG nLen = val.GetLength();
Write_T(nLen);
Write((PCWSTR) val, nLen * sizeof(WCHAR));
}
};struct InStream {
virtual size_t Read(LPVOID pPtr, size_t nSize) = 0;
bool ReadExactTry(LPVOID pPtr, size_t nSize)
{
while (true)
{
size_t nPortion = Read(pPtr, nSize);
if (nPortion == nSize)
return true; // ok if (!nPortion)
return false; // not enough data. nSize -= nPortion;
if (pPtr)
((PBYTE&) pPtr) += nPortion;
}
}
void ReadExact(LPVOID pPtr, size_t nSize)
{
if (!ReadExactTry(pPtr, nSize))
{
// not enough data, raise an appropriate exception throw _T("not enough data!");
}
}
// ordinal types template <class T>
void ReadExact_T(T& val)
{
ReadExact(&val, sizeof(val));
}
// variable-sized strings void ReadExact_Str(CString& val)
{
ULONG nLen;
ReadExact_T(nLen);
PWSTR szPtr = val.GetBuffer(nLen);
ReadExact(szPtr, nLen * sizeof(WCHAR));
val.ReleaseBuffer(nLen);
}
};
现在,让我们实现我们的消息。例如,可以按以下方式声明登录消息:
C++
收缩 ▲
struct MsgLogin
{
// message fields ULONG m_Version;
CString m_Username;
CString m_Password;
MsgLogin()
{
// zero-init members. m_Version = 0;
}
void