引子
有些事情总是来自于平凡的生活,就看有没有仔细去思考原因。
事情是这么发生的,某日,身旁的宁哥负责给一个人电话面试,提到了一个问题“您知道创建对象时加括号与不加括号有什么区别吗?”。首先,澄清一下问题的意思,有如下代码:
class CA
{
public:
CA()= default;
};
int main(void)
{
CA*a1 = new CA();
CA*a2 = new CA;
return0;
}
则a1和a2创建时有何不同?
何谓POD
我的回答自然是加括号与不加括号是一样的,但是后来听到宁哥说“POD类型加括号与不加括号是不一样的”,莫名对自己的观点产生怀疑了。
首先,POD(Plain Old Data,普通的老数据)的定义([ISO/IEC 14882])为:A POD struct is a non-union class that is both a trivial class and astandard-layout class, and has no non-static data members of type non-PODstruct, non-POD union (or array of such types). Similarly, a POD union is aunion that is both a trivial class and a standard layout class, and has nonon-static data members of type non-POD struct, non-POD union (or array of suchtypes). A POD class is a class that is either a POD struct or a POD union.
简单来说,一个POD类,就是一个POD结构体或者POD联合,联合先不管,对于结构体来说,必须即满足“a trivialclass”和“astandard-layout class”,而对于“a trivial class”和“a standard-layout class”来说,定义([ISO/IEC 14882])如下:
Atrivially copyable class is a class that:
— has nonon-trivial copy constructors (12.8),
— has nonon-trivial copy assignment operators (13.5.3, 12.8),
— has nonon-trivial move assignment operators (13.5.3, 12.8), and
— has atrivial destructor (12.4).
A trivial class is a class that has atrivial default constructor (12.1) and is trivially copyable.
[Note: In particular, a trivially copyableor trivial class does not have virtual functions or virtual base classes.—endnote ]
Astandard-layout class is a class that:
— has nonon-static data members of type non-standard-layout class (or array of suchtypes) or reference,
— has novirtual functions (10.3) and no virtual base classes (10.1),
— has the sameaccess control (Clause 11) for all non-static data members,
— has nonon-standard-layout base classes,
— either hasno non-static data members in the most derived class and at most one base classwith
non-static data members, or has no baseclasses with non-static data members, and
— has no baseclasses of the same type as the first non-static data member.
对于上面的描述,从C++对象模型的角度来理解比较容易,其实其本质上就是一个最根本的C++模型。首先,不能存在虚函数,静态函数、成员,因为它们会造成对象分配复杂化(虚表,公用静态区);其次,不能指定任何构造,拷贝,转移等函数,因为定义它们,会导致所有子类及包含此对象的类的这些函数不能产生默认的方法等。
讨论的有点多,按照C++规范,可以使用以下方法来检查一个类是否是POD类型:
std::cout<< std::is_pod<CA>() << std::endl;
注:Visual Studio 2013版本下POD测试与g++ 4.8.1表现不一致,如果使用Visual Studio,则POD类中成员必须定义为public,这与C++标准不一致。所以以下代码测试均才用CodeBlocks+MinGW环境。
POD对象创建
回忆了一下POD类型,发现POD不能自定义构造函数,莫非创建对象时不带括号和带括号真的不一样?于是写下以下测试代码:
class CA
{
public:
CA();
int a;
};
int main(void)
{
CA a1 (); // OK.
CA a2; //Error.
return 0;
}
第一,其实上面这个类型根本不是一个POD类型(违反自定义构造函数规则),只是当时在公司使用VisualStudio 2010开发环境,没有用is_pod去测试,也没有对POD有这么深的认识,基本上都是后来根据这个问题去进一步思考才发现的。
第二,对于这部分代码,感觉自己仿佛脑残了一般,居然真的相信了!幸好又测试了一把new的情况,突然发现,C++有这么一条规则:当一条语句无法判定为声明还是定义时,C++默认解析为声明。所以,
CAa1 ();
被解析为了一个声明,即一个函数声明,一个返回值类型为CA,函数名称为a1的函数。如果有疑问,可以用以下代码测试:
CA a1()
{
std::cout << "I'm a function." << std::endl;
}
int main(void)
{
CA a1();
a1();
return 0;
}
进一步思考
第一,究竟定义对象时带括号和不带括号有区别吗?
我的答案是肯定的:没有。无论是POD类型还是其他类型,本质上都是一样的,之所以有括号和无括号,主要还是当函数有参数时使用。(由于C++规范我没有一行一行去读,所以不知道有这方面的描述。)
第二,关于这个错误的例子。
对于这个错误的例子进行了错误的理解其实是不应该的。思考一下,定义一个对象时,C++具体为你做了什么?无论是在栈上,还是在堆上,第一步肯定是要申请对于大小的内存,第二步则是调用构造函数去初始化这部分内存。而构造函数只有声明,没有定义,这又怎么可能正确呢?其实只是没有调用具体函数产生的假象而已。