条款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中也使用[],反之亦然