关于宏观用法的建议
Sugestions on Macro Usage
最初大多数代码行都很长。我决定使用简洁明了的宏。我一直在想,使用这样的宏是否是一种糟糕的做法。我个人认为宏看起来更干净,但由于宏隐藏了名称和函数,有些人可能会对我如何实现方法感到困惑。
原始代码
...
namespace ConwaysGameOfLife
{
class Grid
{
private:
//Members
...
using set_of_ints = std::unordered_set<int>;
using set_of_sizes = std::unordered_set<std::size_t>;
//Members to be used in Macros
set_of_sizes cells_at_t_minus_one;
set_of_sizes cells_at_t;
...
private:
//Functions
...
//Function showing Lengthy Conditionals
void rule(const std::size_t& cell_position)
{
std::size_t live_neighbors{0};
for(const auto& neighbor_offset : neighbor_offsets)
/*! Total Neighbors !*/
{
//Lengthy Conditional
if(cells_at_t_minus_one.find(cell_position + neighbor_offset) != cells_at_t.end())
{
live_neighbors++;
}
}
//Lengthy Conditional
if(cells_at_t_minus_one.find(cell_position) != cells_at_t.end() and live_neighbors < 2)
/*! Underpopulation !*/
{
cells_at_t.erase(cell_position);
}
//Lengthy Conditional
else if(cells_at_t_minus_one.find(cell_position) != cells_at_t.end() and (live_neighbors == 2 or live_neighbors == 3))
/*! Aging of a Cell !*/
{
cells_at_t.insert(cell_position);
}
//Lengthy Conditional
else if(cells_at_t_minus_one.find(cell_position) == cells_at_t.end() and live_neighbors == 3)
/*! Birth of a Cell !*/
{
cells_at_t.insert(cell_position);
}
//Lengthy Conditional
else if(cells_at_t_minus_one.find(cell_position) != cells_at_t.end() and live_neighbors > 3)
/*! Overpopulation !*/
{
cells_at_t.erase(cell_position);
}
}
public:
...
};
}
...
使用宏的代码
...
#define neighbor cells_at_t_minus_one.find(cell_position + neighbor_offset)
#define cell cells_at_t_minus_one.find(cell_position)
#define dead cells_at_t.end()
#define is_live != dead
#define is_dead == dead
#define result second
namespace ConwaysGameOfLife
{
class Grid
{
private:
//Members
...
using set_of_ints = std::unordered_set<int>;
using set_of_sizes = std::unordered_set<std::size_t>;
//Members used in Macros
set_of_sizes cells_at_t_minus_one;
set_of_sizes cells_at_t;
...
private:
//Functions
...
void rule(const std::size_t& cell_position)
{
std::size_t live_neighbors{0};
for(const auto& neighbor_offset : neighbor_offsets)
/*! Total Neighbors !*/
{
//Macros used
if(neighbor is_live)
{
live_neighbors++;
}
}
//Macros used
if(cell is_live and live_neighbors < 2)
/*! Underpopulation !*/
{
cells_at_t.erase(cell_position);
}
//Macros used
else if(cell is_live and (live_neighbors == 2 or live_neighbors == 3))
/*! Aging of a Cell !*/
{
cells_at_t.insert(cell_position);
}
//Macros used
else if(cell is_dead and live_neighbors == 3)
/*! Birth of a Cell !*/
{
cells_at_t.insert(cell_position);
}
//Macros used
else if(cell is_live and live_neighbors > 3)
/*! Overpopulation !*/
{
cells_at_t.erase(cell_position);
}
}
public:
...
};
}
#undef neighbor
#undef cell
#undef dead
#undef is_live
#undef is_dead
#undef result
...
我决定使用简洁明了的宏。
翻译:我决定c++不适合我,所以我发明了一种新的领域特定语言,它有自己的语法。它看起来真的很好,但由于它在预处理器中完全被翻译,你只能表达我明确预期的概念。
我一直在想,使用这样的宏是否是一种糟糕的做法。
如果说这是一种糟糕的做法,你的意思是随着程序的增长和更改,它将变得无法维护,那么是的,这是一个糟糕的做法。
如果你所说的糟糕做法意味着你的同事会因为你创建了不可维护的代码而对你感到不满,那么是的,这是糟糕的做法。
总之。。。
不仅仅是糟糕的做法,最糟糕的做法。
这:
if (neighbor is_live) { ... }
是绝对不可能理解的。它看起来像是格式错误的代码,任何阅读它的人都会首先认为那里有问题。忘记你的同行吧,这是一种你几个月后就会回来的代码,而且在很长一段时间内都不理解。
它还产生了很多问题,因为像neighbor
、dead
、cell
、result
和is_live
这样的名称是用作该特定程序的标识符的合理名称。因此,如果你碰巧将它们用作标识符,你最终会出现相当难以理解的错误。
考虑替代方案:
if (is_live(cell_position + neighbor_offset)) { ... }
其中我们有一个函数is_live
,它只做正确的事情:
bool is_live(size_t idx) { return !cells_at_t_minus_one.count(idx); }
这是远远优越的,因为:
- 它实际上看起来像是语法正确的代码
- 我们使用的所有变量在代码中都是清晰可见的
- 我可以在任何地方使用我想要的任何标识符,而且我不必担心预处理器会覆盖它们
- 我的可变名称不是一成不变的。如果我想将
neighbor_offset
更改为offset
,那么名称更改仅对neighbor_offset
的作用域有效(在您的代码中,它似乎根本没有被使用)。我不必更改宏的定义
附带说明,您的实时检查是比较一个容器(cells_at_t_minus_one
)和另一个容器的迭代器(cells_at_t
)。
我对宏的经验法则是:
-
尽可能避免使用它们。请改用内联函数或模板,因为宏只是文本替换,可能会导致重大问题。
-
如果你必须使用它们,请非常清楚地命名它们。尽量使用不太可能出现在其他地方的名称,因为宏只是文本替换。
第二步是避免出现以下问题:
#define equals ==
int main()
{
bool equals = 2 equals 3;
if( equals )
printf( "2==3 is true?" );
}
最终为:
int main()
{
bool == = 2 == 3;
if( == )
printf( "2==3 is true?" );
}
当宏被处理时。
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- c++r值引用应用于函数指针
- 如果编译的源代码是特定于它编译的硬件的,我们如何分发它
- 这个指针在c++中的用法
- 如何仅使用对象名称打印特定于对象的成员
- 相当于LocaleMatcher的ICU4C
- 等<thing>效于char32_t
- 类似于strcat()的函数出现问题
- 如何将C++闭包与变量参数同时重用——类似于JavaScript
- 算术运算的结果类似于:C浮点变量中的1/3
- 当C++中需要自动删除时,这是静态的正确用法吗?
- libstdc++ 文件系统中未初始化的用法?
- 相当于 pybind11 中的 boost::p ython py::scope().attr()
- 如何将记忆应用于此递归函数?
- 复制和交换习惯用法与移动操作之间的交互
- 对对应于矩阵的行和列的对向量进行排序
- OpenGL - 在 NDC 中计算位置适用于着色器,但不适用于'regular'程序
- 使用新线程在类似于 Scott Meyer 的单例习惯用法的实现中实例化单例是否安全?
- 当涉及分配器时,是否有类似于复制和交换习惯用法的东西
- 关于宏观用法的建议