看下面这段代码:

int kAmount = 5;

class Strange {
 public:
  Strange();
  Strange(int data);

 private:
  int data_;
};

Strange::Strange() {
  Strange(kAmount);
}

Strange::Strange(int data) : data_(data) {
}

int main() {
  Strange s;
  return 0;
}

注意看 Strange::Strange(void) 的函数体,里面试图调用 Strange::Strange(int)。但按照 C++ 的语法规则,由于变量声明允许在变量名周围加上括号,也就是说,int a 可以被写作 int(a),因此,这里的 Strange(kAmount) 事实上等价于 Strange kAmount。也就是说,这里声明了一个新的局部变量,虽然和全局变量同名,但 C++ 是允许局部变量名称覆盖的。于是,这里继续调用默认构造函数构造一个新的局部变量 kAmount,然后递归进无参数的默认构造函数里面,然后再继续递归,直至堆栈溢出出错为止,根本没去调用 Strange::Strange(int)

C++ 标准规定,当一个语句既可被看作函数调用,又可被看作变量声明的时候,我们将它看作变量声明以消除二义性。当我们调用只有一个参数的构造函数的时候,就很容易遭遇这个二义性。给变量名再加一对括号并不能解决这个问题:

Strange::Strange() {
  Strange((kAmount));
}

这仍然是一个变量声明。

使用类型转换可以消除这个二义性,用 C 风格的转型或 C++ 风格的 static_cast 均可:

Strange::Strange() {
  Strange((int) kAmount);  // C style.
  Strange(static_cast<int>(kAmount));  // C++ style.
}

到这里,问题似乎已经完全解决了,我们完全弄明白了二义性的产生原因和消除的方法,似乎一切都很完美了。但是且慢,还有一个非常严重的问题我们忽略了,那就是:C++ 和 Java 是不一样的,C++ 的构造函数是特殊的,无法级联调用!

众所周知,在 Java 语言中,由于函数不提供默认参数机制,因此人们常常采用重载加级联调用的方式模拟默认参数的功能。尤其是构造函数的级联调用,在 Java 语言里甚至成为了一种设计模式

但在 C++ 里,级联调用普通函数是没有问题的,和 Java 没有什么区别,但要级联调用构造函数,由于在 C++ 中构造函数的特殊性,问题就大了。

看看我们这里的级联调用做了什么事情吧,熟悉 C++ 语法的读者应该已经看明白了,我们在栈上声明了一个新的匿名对象,然后对这个对象调用有一个整型参数的构造函数,将这个对象初始化,然后这个对象离开作用域并析构。这和 Java 中通过级联调用构造函数来初始化当前对象是完全不同的语义!

总之,我们最后的结论是,在 C++ 中,级联调用构造函数是非常错误的做法,你会得到和 Java 非常不一样的,让你大吃一惊的错误结果,永远不要这样写程序。