宏使用建议

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
...

我关于宏的经验法则是:

  1. 尽可能避免使用它们。请改用内联函数或模板,因为宏只是文本替换,可能会导致重大问题。

  2. 如果一定要用到,请把名字说清楚。尝试使用不太可能出现在其他任何地方的名称,因为宏只是文本替换。

第 2 条是为了避免以下问题:

#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?" );
}

当宏被处理时。

这个:

if (neighbor is_live) { ... }

绝对无法理解。它看起来像格式错误的代码,任何阅读它的人首先会认为那里有问题。忘掉你的同行,这是你几个月后会回来但很长一段时间不理解的代码。

它也会产生很多问题,因为像 neighbordeadcellresultis_live 这样的名字是合理的用作此特定程序的标识符。因此,如果您碰巧将它们用作标识符,您最终会遇到相当难以理解的错误。

考虑替代方案:

if (is_live(cell_position + neighbor_offset)) { ... }

我们只有一个函数 is_live 可以做正确的事情:

bool is_live(size_t idx) { return !cells_at_t_minus_one.count(idx); }

这要好得多,因为:

  1. 它实际上看起来像是语法正确的代码。
  2. 我们使用的所有变量在代码中都清晰可见。
  3. 我可以随心所欲地使用任何标识符,而且我不必担心预处理器会覆盖它们。
  4. 我的变量名不是一成不变的。如果我想将 neighbor_offset 更改为 offset,则该名称更改只会对 neighbor_offset 的范围产生影响(在您的代码中甚至似乎没有被使用)。我不必再更改宏的定义!

旁注,您的实时检查是将一个容器 (cells_at_t_minus_one) 的迭代器与另一个容器 (cells_at_t) 进行比较。

I decided to use macros to be short and clear.

翻译:我认为 c++ 不适合我,所以我发明了一种新的领域特定语言,它有自己的语法。它看起来确实不错,但由于它已在预处理器中完全翻译,您只能表达我明确预期的概念。

I was wandering if using macros like this is bad practice.

如果通过不好的做法,你的意思是随着你的程序的增长和变化,它将变得无法维护,那么是的,这是不好的做法。

如果您所说的不良做法是指您的同事会因为您创建无法维护的代码而生气,那么是的,这是不良做法。

总结...

不只是糟糕的做法,最糟糕的做法。