默认构造函数与空括号

228

空圆括号(小括号)表示调用C++中的默认构造函数,在这种情况下,是否存在任何合理的原因使其无效?

MyObject  object;  // ok - default ctor
MyObject  object(blah); // ok

MyObject  object();  // error

我似乎每次都会自动输入“()”。这样做不允许有好的理由吗?


有人应该想出一个更好的标题,但我想不到会是什么。至少拼写出“构造函数”以帮助搜索引擎。 - Adam Mitz
1
这只是另一个展示C++上下文敏感性的好例子。如果“blah”是一个类,那么问题中的示例代码也会失败。 - Albert
1
我注意到的一件事是,如果我只有默认构造函数,那么编译器在使用()时不会给出任何错误,例如MyObject object会像往常一样工作,而MyObject object()不会给出任何错误!请问有人能解释一下为什么吗?我的意思是我没有在main中定义这个函数...所以它应该会报错,对吧?提前感谢! - Milan
9个回答

193

最令人烦恼的解析

这与所谓的"C++最令人烦恼的解析"有关。基本上,任何编译器可以解释为函数声明的内容都将被解释为函数声明。

同样问题的另一个实例:

std::ifstream ifs("file.txt");
std::vector<T> v(std::istream_iterator<T>(ifs), std::istream_iterator<T>());

v被解释为带有2个参数的函数声明。

解决方法是添加另一对括号:

(v)

std::vector<T> v((std::istream_iterator<T>(ifs)), std::istream_iterator<T>());

或者,如果您有C++11和列表初始化(也称为统一初始化)可用:

std::vector<T> v{std::istream_iterator<T>{ifs}, std::istream_iterator<T>{}};

有了这个,它就不可能被解释为函数声明。


8
吹毛求疵:你可以在函数内部声明函数。在 C 语言中,这被称为“局部函数”,并且至少 extern "C" foo(); 这样的语法在 C++ 中也是允许的。 - Marc Mutz - mmutz
4
那怎么可以解释为一个函数呢? - Casebash
14
@Casebash,“std::vector”是返回类型,“v”是函数名称,“(”开启形式参数列表,“std::istream_iterator”是第一个参数的类型,“ifs”是第一个参数的名称,围绕“ifs”的“()”实际上被忽略了;第二个“std::istream_iterator”是第二个未命名参数的类型,“()”围绕它也被忽略了;“);”关闭参数列表和函数声明。 - Constantin
2
在与表达式语句和声明相关的语法中存在一个歧义:具有函数样式显式类型转换作为其最左子表达式的表达式语句可能与以(开头的声明无法区分。在这些情况下,该语句是一个声明。(C++ ISO/IEC (2003) 6.8.1) - bartolo-otrit
12
@Constantin,第二个参数后面的括号不会被忽略。第二个参数不是一个 std::istream_iterator,而是指向一个没有参数并返回 istream_iterator 的函数的指针/引用。 - CTMacUser
显示剩余6条评论

125

因为它被视为一个函数的声明:

int MyFunction(); // clearly a function
MyObject object(); // also a function declaration

1
但是它应该会报错,对吧?因为我们还没有定义 object() 函数,你能详细解释一下吗?我现在有点困惑。非常感谢您的帮助! - Milan
顺便提一下,在我的 main 函数中,我甚至尝试了这些:any_variable_name random_function_name(),例如 int func1()double func2()void func3() 等等,它们都可以正常工作,也就是说我的程序编译时没有出现任何错误!然而,我并没有定义这些函数,所以应该会出错才对,不是吗? - Milan
1
@Milan 如果你真的尝试调用那些函数,我会预期链接器错误。否则它们只是声明。 - 1800 INFORMATION

52

相同的语法也用于函数声明 - 例如,函数 object 接受零参数并返回 MyObject


2
谢谢 - 我不会想到在其他代码中间声明一个函数。但我想这是合法的。 - Martin Beckett

11

编译器认为这是一个不带参数并返回MyObject实例的函数声明。


7
我猜编译器不知道这个语句是一个构造函数调用还是一个函数原型,该函数名为 object,返回类型为 MyObject,没有参数。

7
您还可以使用更冗长的构造方式:

例如:

MyObject object1 = MyObject();
MyObject object2 = MyObject(object1);

在C++0x中,这也允许使用auto:
auto object1 = MyObject();
auto object2 = MyObject(object1);

5
需要一个拷贝构造函数,而且效率低下。 - Casebash
9
编译器可能足够聪明,会使用类似于RVO的优化来防止不效率。 - dalle
2
“Probably” 的意思是 “我猜测”。关于优化,人们通常不想猜测,而是采取明确的方式。 - Stefan
7
@Stefan: 你不需要“猜测”,所有主流编译器都会发生拷贝省略,这已经持续了十多年。并不是说这是好的代码。 - Lightness Races in Orbit
@Casebash,自从C++11以来,将使用移动赋值运算符,对于在堆上持有资源的类来说,这是更快的。 - yanpas
1
自C++17开始,不存在复制/移动,行为定义与直接初始化相同。 - M.M

5
从n4296 [dcl.init]:
[注意:
由于语法不允许initializer使用(),因此X a();不是类X的对象声明,而是声明了一个不带参数并返回X的函数。在某些其他初始化上下文中(5.3.4、5.2.3、12.6.2),()形式是被允许的。
—end note] C++11 链接
C++14 链接

3
你能否加上一个来源链接? - Felipe Tonello

5

如多次提到的那样,这是一个声明。为了向后兼容而采用这种方式。由于其历史遗留问题,C++存在许多不一致、痛苦和虚假的领域。


3

正如其他人所说,这是一个函数声明。自C++11以来,如果您需要看到明确告诉您使用默认构造函数的空something,则可以使用大括号初始化。

Jedi luke{}; //default constructor

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