【问题标题】:Creating a new scope with a C++ Macro?使用 C++ 宏创建新范围?
【发布时间】:2013-08-14 23:23:19
【问题描述】:

这甚至可能吗?我想编写一个宏,以便更轻松地使用我的某些类功能。

假设我的类中有 2 个成员函数 setup() 和 cleanup(),其中 setup() 为需要在自己的范围内执行的某些操作设置参数,而 cleanup() 执行清理(类似构造函数和析构函数的概念)。

目前,我这样做:

myClassInstance.setup(); //call the setup function
{ //start scope
    //CREATE LOCAL VARS
    //DO STUFF IN THIS SCOPE
    myClassInstance.cleanup(); //cleanup
} //end scope, destroy locals

但想做这样的事情:

NEWSCOPE(myClassInstance) //calls setup()
{
    //CREATE LOCAL VARS
    //DO STUFF IN THIS SCOPE
} // calls cleanup() and destroys locals

我的想法是写一个宏类,可以在使用宏时实例化,并且 setup() 和 cleanup() 可以在构造函数/析构函数中实现......或类似的东西......

这是考虑这个问题的正确方法,还是有另一种方法可以编写一个基本上可以环绕用户编写的代码的宏?

* 编辑 * 我修复了命名约定,因为函数名称会引起混淆。

【问题讨论】:

  • 这种事情在 OpenSceneGraph 中经常使用 aaronman 的回答来完成
  • 但我不想显式调用 startScope() 和 endScope() 函数并消除用户忘记 { } 的可能性。
  • 如果所有代码都旨在防止懒惰的程序员错误地使用库的可能性,那么这个星球就会陷入停顿。
  • @radensb 看看我更新的答案,如果没有点击,我想我会删除我的答案
  • 我猜你正在使代码难以阅读和维护。从长远来看,这将伤害您或未来的员工

标签: c++ macros scope


【解决方案1】:

要创建一个新范围,只需使用匿名块。

{ 
    Obj obj;
    /* 
    teh codez
    */
}//obj is deallocated

所以你不需要宏

听起来你 startScopeendScope 实际上应该是构造函数和析构函数,但是如果不知道它们实际上做什么,就很难知道

更新:我试图给你一个答案,但我只会咆哮。

类似于构造函数和析构函数的概念

在我看来,它们是构造函数和析构函数,当您让构造函数和析构函数进行设置和清理时,RAII 将自然而易读地执行操作。

另一件事,你说你的第一个解决方案(我有点不小心把它给了你)正在工作,为什么需要使用宏的解决方法,在 C 宏中需要模拟 C++ 提供的功能(如模板和对象)。对于几乎所有情况,尤其是在 C++11 中,宏只会让事情变得更糟,更难调试,在你的情况下,看起来你在执行宏时实际上必须输入更多内容?

我的建议是重新考虑为什么需要宏以及为什么setupcleanup 不能成为构造函数和析构函数。

【讨论】:

  • 这就是我已经在做的事情,但是我必须在开头和结尾同时调用 startScope 和 endScope() 。我正在编写一个库,并希望使其尽可能简单易用。使用宏可以强制一个新的范围(我希望)并简化使用。
  • @radensb 这确实强制一个新的范围,分配堆栈上的对象,它们将在这个范围的末尾被释放,我不明白你怎么能希望比这更简单
  • @radensb 可能我误解了你的问题,因为我不知道 startscopeendscope 函数是做什么的,但事实是宏不应该真正在 c++ 中使用
  • @radensb 也许在您的 API 中使用术语“范围”会产生误导。也许有一个更好的名字,以避免与“范围”的正常含义混淆。
  • 这似乎仍然是 RAII 的工作。 “setup”和“cleanup”分别是构造函数和析构函数的用途。如果你的构造函数和析构函数真的在做他们的工作,你可以说像{ MyClass myClassInstance; /* do your stuff here */ },就像你试图做的那样,它都会被清理掉。
【解决方案2】:

您可以像使用 RAII 获得互斥锁一样对待它。像这样的:

class MyClassScopeBlock
{
  public:
    MyClassScopeBlock( MyClass & c )
        : obj(c)
    {
        obj.startScope();
    }

    ~MyClassScopeBlock()
    {
        obj.endScope();
    }

  private:
    MyClass & obj;
};

然后将其实例化为作用域块内的局部变量:

{
    MyClassScopeBlock block( myClassInstance );
    //CREATE LOCAL VARS
    //DO STUFF IN THIS SCOPE
}

如果你真的想要,你可以为它定义一个宏,在作用域块内使用:

#define NEWSCOPE(inst) MyClassScopeBlock block(inst)

就个人而言,我更喜欢尽可能远离宏。

【讨论】:

  • 这真的很接近我的想法,但如果可能的话,我希望看起来像我的第二个例子。如果没有,这可以工作。我已经研究了一段时间的宏观技巧,其中一些真的很巧妙。我希望有办法做到这一点。
  • 在这种情况下,请查看 KugBuBu 的回答。无论如何,玩宏。你会发现,你做的越多,你就越想避免它们。我所描述的 RAII 范式对于了解 C++ 的人来说非常常见且直观。在某些时候,您需要将责任转嫁给使用您的代码的人,并停止试图保护他们。 =)
  • @paddy cmon KugBuBu 的答案有一个宏,它的唯一目的是成为一个结束括号(}),如果他接受我放弃 stackoverflow
【解决方案3】:

在看到 BOOST_FOREACH 宏后,我花了几个小时试图弄清楚如何使宏控制范围。在弄清楚它的过程中,我遇到了这个问题,希望它能够回答!但是,不完全是。所以,我通读了所有的code for the BOOST_FOREACH 和原始的design for BOOST_FOREACH。然后我觉得有点愚蠢......宏基本上将代码直接插入到它所在的位置。这意味着我们可以有一个宏:

#define LOOP_3() \
    for(int i = 0; i < 3; ++i)

现在,让我们测试一下吧!

LOOP_3() std::cout << "Hello World!" << std::endl;
/* === Output ===
Hello World!
Hello World!
Hello World!
*/

耶!但是,这有什么用呢?那么,在循环结束时i 会发生什么? 调用了析构函数,对于i 来说不是太花哨,但想法就在那里。

我们现在只需要一个类来处理这个问题:

class SCOPE_CONTROL {
public:
    SCOPE_CONTROL(): run(1) { std::cout << "Starting Scope!" << std::endl; }
    ~SCOPE_CONTROL() { std::cout << "Ending Scope!" << std::endl; }
    bool run;
}

让我们使用那个吸盘!

#define NEWSCOPE() \
    for(SCOPE_CONTROL sc = SCOPE_CONTROL(); sc.run; sc.run = 0)

...
NEWSCOPE()
    std::cout << "    In the Body!" << std::endl;
std::cout << "Not in body..." << std::endl;
...

/* === Output ===
Starting Scope!
    In the Body!
Ending Scope!
Not in body...
*/

要使用setupcleanup 函数,只需稍作改动!

class SCOPE_CONTROL {
public:
    SCOPE_CONTROL(MyClass myClassInstance): control(myClassInstance), run(1) { 
        control.setup();
    }
    ~SCOPE_CONTROL() { control.cleanup(); }
    bool run;
    MyClass & control;
}
#define NEWSCOPE(control) \
    for(SCOPE_CONTROL sc = SCOPE_CONTROL(control); sc.run; sc.run = 0)

...
NEWSCOPE(myClassInstance)
{
    // CREATE LOCAL VARS
    // DO STUFF IN THIS SCOPE
}   // end scope, destroy locals
...

为了更好地使用ENCODED_TYPE(如何在BOOST_FOREACH的设计中非常简单!)允许SCOPE_CONTROL成为模板类型。

【讨论】:

  • 我无法弄清楚的唯一问题是,如果在范围区域中有一个变量sc,它会因为宏中的sc 而引发错误。此外,sc 对象可以在范围内进行编辑。
【解决方案4】:

将整个作用域放入宏替换的更好替代方法是使用finally block 之类的东西。我已经成功地用这些宏封装了链接的解决方案:

#define FINALLY_NAMED( NAME, ... ) auto && NAME = \
        util::finally( [&]() noexcept { __VA_ARGS__ } );
#define FINALLY( ... ) CPLUS_FINALLY_NAMED( guard, __VA_ARGS__ )
#define DO_FINALLY static_cast< void >( guard );

用法:

{
    myClassInstance.setup(); //call the setup function
    FINALLY ( myClassInstance.cleanup(); ) //call the cleanup function before exit

    // do something

    DO_FINALLY // Explicitly note that cleanup happens here. (Only a note.)
}

这是异常安全的,当且仅当setup 成功完成时,cleanup 才会执行,就像构造函数/析构函数对一样。但是cleanup一定不能抛出异常。


但是如果你想用老式的方式来做……

您可以使用可变参数宏将整个范围包含在宏中:

#define NEWSCOPE( INSTANCE, ... ) { \
    (INSTANCE).setup(); /* call the setup function */ \
    { __VA_ARGS__ } /* paste teh codez here */ \
    (INSTANCE).cleanup(); /* call the cleanup function */

我建议不要将cleanup 放在内部作用域内,因为作用域的意义在于包含声明和名称,但您想在外部作用域中使用INSTANCE 的名称。

用法:

NEWSCOPE ( myClassInstance,
    // Do stuff.
    // Multiple declarations, anything can go here as if inside braces.
    // (But no #define directives. Down, boy.)
)

【讨论】:

  • 我不认为这会像你认为的那样......放入'...'的参数不会被放入VA_ARGS吗?我需要将代码体放在那里,这不是这样做的。
  • @radensb 整个代码体将被... 接受并替换为__VA_ARGS__。单个参数的概念被打破;实际上它是一个可能包含逗号的参数。 (否则逗号未嵌套在括号中会导致问题。)这实际上是可变参数宏的典型用法,因为没有用于迭代项目的宏工具。无论如何,我向你保证我知道我在做什么,因为我使用这个成语来编写我自己的预处理器:)
  • @radensb 询问,您将收到。但我强烈推荐第一个例子。如果没有其他东西在宏扩展中包含代码会使您的编译器在格式化错误消息时跳过一个箍。
  • 非常酷。我不知道宏可以做到这一点。我也开始明白为什么在 C++ 中避免使用它们,但这很整洁。感谢您的建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-25
  • 1970-01-01
  • 1970-01-01
  • 2015-04-17
  • 2014-03-05
  • 1970-01-01
相关资源
最近更新 更多