关于宏观用法的建议

Sugestions on Macro Usage

本文关键字:用法 于宏观      更新时间:2023-10-16

最初大多数代码行都很长。我决定使用简洁明了的宏。我一直在想,使用这样的宏是否是一种糟糕的做法。我个人认为宏看起来更干净,但由于宏隐藏了名称和函数,有些人可能会对我如何实现方法感到困惑。

原始代码

...
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) { ... }

是绝对不可能理解的。它看起来像是格式错误的代码,任何阅读它的人都会首先认为那里有问题。忘记你的同行吧,这是一种你几个月后就会回来的代码,而且在很长一段时间内都不理解。

它还产生了很多问题,因为像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)。

我对宏的经验法则是:

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

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

当宏被处理时。