C++代码中多余的花括号

14
有时候你会遇到一些代码里有额外的花括号,这些花括号与作用域无关,只是为了提高可读性和避免错误。
例如:
GetMutexLock( handle ) ; 
{
  // brace brackets "scope" the lock,
  // must close block / remember
  // to release the handle.
  // similar to C#'s lock construct
}
ReleaseMutexLock( handle ) ;

我在其他地方也见过它:

glBegin( GL_TRIANGLES ) ;
{
  glVertex3d( .. ) ;
  glVertex3d( .. ) ;
  glVertex3d( .. ) ;
} // must remember to glEnd!
glEnd() ; 

如果忘记释放互斥锁(假设您记得}和Release()调用),则会引入编译器错误。

  1. 这是一种不好的实践吗?为什么?
  2. 如果不是一种不好的实践,它是否会改变代码的编译方式或使其变慢?

2
在OpenGL中,这种做法旨在增强代码的可读性。在glBegin()调用中使用括号作为顶点组的视觉分隔符,以便绘制。 - karlphillip
这是一个可怕的做法!为什么有人会使用块分隔符来突出一段代码,这超出了我的理解范围。我可以想象他们错过了所有微小的优化...所有这些都为了像可读性这样的次要问题而牺牲。这个世界要变成什么样子啊??? - Edward Strange
1
@Noah,我已经把你改名为讽刺猫了。 - bobobobo
8个回答

35

花括号本身没问题,它们只是限制作用域,不会减慢任何东西。这可以被视为更清晰的方式。(总是优先选择干净的代码而不是快速的代码,如果更干净,就不要担心速度,直到您进行剖析。)


但是在资源方面,这是一种不好的做法,因为您已经把自己置于资源泄漏的位置。如果块中出现任何异常或返回,那么你就惨了。

使用范围绑定资源管理(SBRM,也称为RAII),通过使用析构函数将资源限制在一个范围内:

class mutex_lock
{
public:
    mutex_lock(HANDLE pHandle) :
    mHandle(pHandle)
    {
        //acquire resource
        GetMutexLock(mHandle);
    }

    ~mutex_lock()
    {
        // release resource, bound to scope
        ReleaseMutexLock(mHandle);
    }

private:
    // resource
    HANDLE mHandle;

    // noncopyable
    mutex_lock(const mutex_lock&);
    mutex_lock& operator=(const mutex_lock&);
};

所以你得到:

{
  mutex_lock m(handle);
  // brace brackets "scope" the lock,
  // AUTOMATICALLY
}

对于所有资源都要这样做,这样更加干净、安全。如果你处于“我需要释放这个资源”的位置,那么你做错了;它们应该自动处理。


7
+1 表示正确,而这个我不知道的新缩写 SBRM!在表达意图方面比 RAII 更好。甚至连维基百科页面都没有! - Matthieu M.

18

大括号影响变量作用域,据我所知这就是它们的全部作用。

是的,这可能会影响程序的编译。析构函数将在块的结尾调用,而不是等到函数结束再调用。

通常这正是你想要做的。例如,你的 GetMutexLock 和 ReleaseMutexLock 可以更好地使用以下方式编写 C++ 代码:

struct MutexLocker {
  Handle handle;
  MutexLocker(handle) : handle(handle) { GetMutexLock(handle); }
  ~MutexLocker() { ReleaseMutexLock(handle); }    
};
...
{
  MutexLocker lock(handle);
  // brace brackets "scope" the lock,
  // must close block / remember
  // to release the handle.
  // similar to C#'s lock construct
}

使用这种更符合C++风格的方式,在块结束时自动释放锁。在所有情况下都会释放锁,包括异常,除了setjmp / longjmp或程序崩溃或中止之外。


12
你需要为这个储物柜命名,否则它只是一个立即消失的临时对象。并且你应该更改之后的注释以匹配新的资源管理方式。 - GManNickG
正确的语法是 MutexLocker handle;,而不是 MutexLocker(handle); - kennytm
3
@KennyTM 不是应该用 MutexLocker lock(handle); 吗? - Pedro d'Aquino
@Pedro:没错。我以为 handle 是变量名字 :P - kennytm
我进去修复了那个问题。这是最受欢迎的答案,应该不会太错。@Zan:希望你不介意。 - sbi
@sbi,@GMan:谢谢。天啊!我不应该忘记写那个的。 - Zan Lynx

3

这不是一种不好的做法。它不会使任何东西变慢,只是一种代码结构化的方式。

让编译器为您执行错误检查和强制执行始终是一件好事!


3
在您最初的示例中,{ ... } 的具体放置仅作为格式化语法糖,使逻辑相关语句组的开始和结束更加明显。正如您的示例所示,它对编译后的代码没有任何影响。
我不知道您所说的“如果未释放互斥锁,这将引入编译错误”的含义是什么。这简直是不真实的。这种使用{ ... }的方式不能引入任何编译错误。
是否采用这种方法是个人喜好问题。看起来还可以。或者,您可以使用注释和/或缩进来表示代码中语句的逻辑分组,而不需要额外的{ ... }
有各种基于作用域的技术可供选择,其中一些技术已在其他答案中进行了说明,但您在OP中拥有的与此完全不同。再次强调,您在OP中拥有的(如所示)纯粹是一种源格式化习惯,带有对生成代码没有任何影响的多余{ ... }

似乎只有你评论了“编译错误”的问题 - 观察得很好。我的印象是发帖者看到类似这样使用宏:BEGIN_X // do stuff END_X其中BEGIN_X和END_X替换包括它们自己的{和},因此缺少END_X确实会产生编译器错误。丑陋的代码。 - Tony Delroy
@Tony:Boost 库有几个与此相关的工具。有一次我忘记了其中之一,结果得到了大约 100 页的错误代码。"丑陋的东西"已经无法形容它了 :) - Dennis Zickefoose

2

这不会对编译代码产生任何影响,除了在该块结束时调用任何析构函数,而不是在周围块的结尾调用。除非编译器完全疯狂。

个人认为这是一种不好的做法;避免在此处可能出现的错误类型的方法是使用作用域资源管理(有时称为RAII),而不是使用易错的排版提醒。我会将代码写成类似以下内容:

{
    mutex::scoped_lock lock(mutex);
    // brace brackets *really* scope the lock
}   // scoped_lock destructor releases the lock

{
    gl_group gl(GL_TRIANGLES); // calls glBegin()
    gl.Vertex3d( .. );
    gl.Vertex3d( .. );
    gl.Vertex3d( .. );
} // gl_group destructor calls glEnd()

2

如果你将代码放在大括号中,那么你可能应该将其分解成自己的方法。如果它是一个单一的离散单元,为什么不对其进行标记并使其功能分解?这样可以明确块的作用,后来读取代码的人不必费劲去理解。


1

在C++中,这个更有用(我个人认为),因为有对象析构函数;而你的例子是用C写的。

试想一下,如果你创建了一个MutexLock类:

class MutexLock {
private:
    HANDLE handle;
public:
    MutexLock() : handle(0) {
        GetMutexLock(handle);
    }

    ~MutexLock() {
        ReleaseMutexLock(handle);
    }
}

然后,您可以通过使用大括号提供新的作用域,将该锁限定为仅适用于需要它的代码:

{
    MutexLock mtx;  // Allocated on the stack in this new scope

    // Use shared resource
}
// When this scope exits the destructor on mtx is called and the stack is popped

1

在我看来,任何能提高可读性的做法都是好的实践。如果添加大括号有助于提高可读性,那就去做吧!

添加额外的大括号不会改变代码编译的方式。它不会使程序运行变慢。


我认为添加大括号会降低可读性。大括号告诉我“这里有一个控制结构”。当没有控制结构时,我仍然会寻找一会儿。让我花费认知和一小部分时间去寻找不存在的东西并不是可读性。 - David Thornley
@David - 我认为原帖作者并没有考虑像你的例子中那样向代码中添加任意数量的大括号。我所指的是原帖作者使用大括号的方式。显然,像你的例子中添加大括号并不能提高可读性。 - Starkey
在我的看法中,除了指示控制结构之外,使用大括号没有助于可读性。这不算作控制结构。而将它们用于限制 RAII 分配的资源的范围则是可以的。 - David Thornley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接