C++ 杂谈(1)

static vs anonymous namespace

static 在 C/C++ 里面是一个令人迷惑的的关键字:

  • 类的成员、方法可以是 static 的,它表示这是一个类相关的成员、方法,而并不属于这个类的对象,换言之对对象做 sizeof 的时候这个空间不计入其中,方法调用的时候不需要传递 this 指针
  • 函数里面可以声明 static 变量,这其实是隐藏在函数 scope 里面的全局变量,这意味着多次函数调用中其值的变化可以保留,前面的调用会影响后面的调用,也意味着多线程环境下这是非常不安全的
  • 同时自由的函数、变量(全局变量)也可以用 static 修饰表示这是一个尽在当前编译单元中可见的,那么如果其他编译单元需要连接这个函数,linker 会说对不起没看见,常常用于一些 implementation 相关的函数,不希望产生多编译单元之间的命名冲突

C++ 提供了另外一个据说“underuse”的 feature 就是 anonymous namespace。通过 anonymous namespace 也可以将一些需要隐藏在当前编译单元的东西包裹起来,那么和 static 在这个功能上的区别究竟是什么?主要的区别在于,static 作用的对象是对象、函数,却不能对类型的声明进行类似的作用,而 anonymous namespace 对两者均有效。

static 在 scoping 上的功能其实是被 deprecated,因此应该多用后者。

static vs runtime binding

这是一个老生常谈的问题,但是我被震惊的是很多人设计的时候都没想清楚就选择了一个不应该的,导致了严重的后果。一个典型的例子是,假定有若干个现成的 IO 实现,为了你自己的应用,希望 abstract out 这个区别,不过有一点需要注意的是某些 IO 仅对某一类数据类型有效。对于应用来说,如何 bind 是一个很有意思的事情:

  • 假如你是做 UI 的,你很可能希望有个 runtime binding,因为你可能希望将这个 delay 到用户需要决定的时候
  • 假如你是写 pipeline 的,程序运行起来中途完全没必要改变 IO 类型的,你可能完全不需要 runtime binding

类似的,写优化算法,写 learning 算法,compile time/static binding 已经足够满足大家的需要了。这就跟选择容器类一样,你很少需要中途把一个 vector 换成 set。下面到执行层面,你可以为这个 abstraction 选择

  • 一个父类,这就是 Java 里面 interface 的玩法了,全部是 virtual function,不同 IO 选择不同的子类
  • 一个 concept,这是 C++ 典型的 template 的玩法

两个做法导致了不同的用途:

  • 因为存在公共祖先,因此可以 runtime assign 对应的子类实现,但是这样一来很可能有限制的 IO 类只能在运行时给出出错信息,比如某个 interface 抛出异常(比如通过 dynamic_cast 发现失败了)而不能干别的事情
  • compile-time 类型 checking 出错就可以在编译时报错(good),如果需要 runtime 决定的话可能会有一些 type 上的 issue

我见到的另外一个做法是:弄了一个类,通过 enum 选择 IO 类型,IO 的不同 binding 居然都在这个类里面。可是这个很明显的造成了严重的问题:如果你希望对一个多数 IO 并没有限制的类型创建这个对象,因为实例化之后类型是运行时决定的,所有 IO 的方法必须在编译时生成,那么那些有限制的 IO 代码就跟 compile-time binding 的一样报错了,也就是说这种 code 只在所有 IO 支持的类型的交集上能 work,这也许是我见过的最不经思考的 code 了… 据说当时有人讨论,可是为啥还通过了…

至于怎样的设计能使得 static binding 也能产生 runtime 的行为,这是一个很有意思的问题,我们后面专门来看看。

unit testing 与 mocking

Java 的世界里 unit testing 重要的两个 framework、JUnit 和 TestNG,testing 里面常用的 mocking 技巧如 PowrMock、Mockito 等等。C++ 的世界里没有了 reflection、dynamic proxy,testing、mocking 变成了各种黑科技的场所。google 表示咱有 open source 的,大家赶紧来用吧,这就是 googletest 提供的 gUnit 和 gMock;除此以外,boost 有自己的 testing 但并没有内置 mocking 的策略,好在 turtle 的存在弥补了这个缺陷。这里我们简单的比较一下两个 framework。

gunit/gmock 才开始玩,使用起来大致分这么几步:

  • 创建一个 FooTest 继承 ::testing::Test,通过构造函数或者 protected 的 void SetUp() 初始化成员
  • 一般的 test case 可以直接用 TEST 宏生成,更多的需要用 fixture 的使用 TEST_F 宏
  • mocking 其实还是得自己再写个类,然后 gmock 只能处理 virtual 函数,如果是 protected 的话必须 promote 到 public,使用 MOCK_METHODn 宏将 virtual 函数覆盖掉,注意 friend 是不行的,大概是因为每个 test case 会产生一个类并注册到 FooTest
  • 测试里面提供了 EXPECT_* 宏,对 mock 可以用一些 DSL 来匹配参数、调用次数等
  • 最后要写个简单的 main trigger 所有的 test case

整体看起来比较简洁,但是也能看出来有一定的限制。相对来说 boost.test 存在已经有一段时间了,有不少人抱怨其 quality 不够(见这里),但是其实还是有不少项目依赖它的。相比来说也有人认为 google test 的风格是 C++ 90′ 年代的(窃以为这是 google 那诡异的 styleguide 决定的)。很显然从前面链接里面的讨论依稀可以看出来 boost.test 主要的问题是文档结构没有突出用户部分,这导致连 Dave 那种高手都觉得读完了无所适从。这也是我读了半天 boost.test 的文档觉得 I didn’t get it 的原因,觉得这个系列可能会让人觉得至少不必 google test 难用吧。

其实一直在想既然 test 本身需要一定的语言级别或者标准库级别的支持,为什么不能让 release 和 testing 共享一套编译流程,但在 testing 编译时注入 reflection,允许额外的一些 API 用来将测试更容易的实现,这样避免 testing framework 使用种种黑科技造成用户觉得各种诡异呢?

——————
And so commanded he the second, and the third, and all that followed the droves, saying, On this manner shall ye speak unto Esau, when ye find him.

Advertisements
C++ 杂谈(1)

一个有关“C++ 杂谈(1)”的想法

    1. zt 说:

      说是 undeprecate 了,感觉只是为了 C-compatibility,对纯粹的 C++ project 来说,是不是还是多用 anonymous namespace 好呢?

      Catch 看起来非常 neat 的一个 testing framework,有没有跟 boost.testing 和 google test 的比较呢?

      1. 在标准化 C++98 的过程中委员会 deprecate 了一些东西仅仅因为有更新的,但现在认为这是没有意义的。

        C++ 里 deprecate 一个特性,这个特性必须完全失败。「设计不够好」是远远不够的。

        并且,新特性要能完全取代以前的 use case,但 unamed namespace 不能完全 cover,有一个例外,见 http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=211 “Deprecated, alas Unavoidable”.

        我个人觉得 static 是很好的,因为它能作用于行,你可以在 diff 中看出变化。

        Boost.Test 有用;新的项目在用 Catch. 它的 prebuild 版本就一个头文件,直接包含进代码树都行。不需要一堆断言,断言自己能展开表达式这个特性应该是独此一家。目前感觉没有什么被比下去的部分。

    2. zt 说:

      粗粗看了一下决定有机会试用一下,我现在工作用 google test,业余用了一下 boost.test,暂时对这些都没有发言权 🙂

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s