条款5:了解c++默默编写并调用哪些函数
当我们写一个空类时,编译器会自动帮我们生成默认构造函数、析构函数、拷贝构造、以及赋值运算符。
因此对于这个类:
class Demo {};
编译器会生成:
class Demo {
public:
Demo() {...}
Demo(const Demo& demo) {...}
~Demo() {...}
Demo& operator=(const Demo& demo) {...}
};
知识点:空类的大小为什么为1?
sizeof(Demo) == 1
试想如果类的大小为0,那么在类实例化多个对象时,在地址上就没办法区分类的实例化,所以,为了让每个实例化都有一个唯一的地址,编译器往往会给空类默认加一个字节,这样空类实例化后就得到了唯一的地址,所以空类所占大小为1个字节。
对于下面这个例子:
class Demo {
public:
void setStr(string str) {
this->str = str;
}
string getStr() {
return this->str;
}
void setNumber(int iNumber) {
this->iNumber = iNumber;
}
int getNumber() {
return this->iNumber;
}
private:
string str;
int iNumber;
};
int main()
{
Demo demoA;
demoA.setStr("hello");
demoA.setNumber(123);
Demo demoB(demoA);
std::cout << demoB.getStr() << std::endl;
std::cout << demoB.getNumber() << std::endl;
return 0;
}
我们并没有写Demo类的拷贝构造,但同样得到了正确的输出,这正是编译器的功劳:
hello
123
值得注意的是,Demo的成员变量中有一个string类型的变量,在拷贝时会调用string类的拷贝构造函数完成拷贝。
而int类型的iNumber则是按每一bits完成拷贝。
string类包含默认拷贝构造函数,那如果成员变量包含一个不可拷贝的对象时,又会发生什么呢?
class UnCopyClass {
public:
UnCopyClass() {}
private:
UnCopyClass(const UnCopyClass&);
};
class Demo {
public:
Demo() {}
private:
UnCopyClass uncopyclass;
};
int main()
{
Demo demoA;
Demo demoB(demoA);
return 0;
}
编译出错:
error C2280: “Demo::Demo(const Demo &)”: 尝试引用已删除的函数
因为Demo类中包含一个不可拷贝的成员变量,默认的拷贝构造函数不知道该怎么办了,这时就需要我们自己实现拷贝构造,即使拷贝时什么也不干(即使想拷贝uncopyclass也拷贝不了)
class Demo {
public:
Demo() {}
Demo(const Demo&) {
}
private:
UnCopyClass uncopyclass;
};
这样的话编译就不会报错了,只是这个拷贝什么都没干。
条款16:成对使用new和delete时要采取相同形式
首先来说new和delete应该配对使用,而条款中所说的采取相同形式又是什么意思呢?
来看一个例子:
class Demo {
public:
Demo() {
cout << "defalut constructor" << endl;
}
~Demo() {
cout << "destructor" << endl;
}
};
int main()
{
Demo *demo = new Demo();
delete demo;
return 0;
}
程序输出:
defalut constructor
destructor
没有问题,但是对于下面这个程序输出呢?
int main()
{
Demo *demo = new Demo[5];
delete demo;
return 0;
}
程序输出:
defalut constructor
defalut constructor
defalut constructor
defalut constructor
defalut constructor
destructor
可以发现只调用了一次析构函数,并且我的电脑还弹出了:
但是当我们使用
int main()
{
Demo *demo = new Demo[5];
delete[] demo;
return 0;
}
程序不仅得到了正确的输出,而且没有报错
defalut constructor
defalut constructor
defalut constructor
defalut constructor
defalut constructor
destructor
destructor
destructor
destructor
destructor
分析一下,我们new的是一个Demo类型的数组,因此在delete时也需要表明,需要delete的是一个数组,这样才会依次调用数组中每个对象的析构函数,得到正确的结果。
那如果我们对单个对象执行delete[]
呢?
int main()
{
Demo *demo = new Demo();
delete[] demo;
return 0;
}
运行程序发现,程序不断的输出destructor
正如书中所说delete会读取若干内存并把它解释为“数组大小”,结局让人不太愉快。
因此如果在new时使用了[],那么必须在相应的delete中也使用[],反之亦然