Item05 了解C++默默编写并调用了哪些函数
1. 定义空类并使用空类定义变量的时候,编译器会默认生成以下函数:
- (无参)默认构造函数
- 拷贝构造函数
- 拷贝赋值函数
- 析构函数
// 只有需要的时候才会创建
class Empty {
public:
Empty() {} // 默认构造函数
Empty(const Empty& rhs) {} // 默认拷贝构造
~Empty() {} // 默认析构函数
Empty& operator=(const Empty& rhs) {} // 默认拷贝赋值函数
};
2. 如果显式定义了其中的任何一个特殊成员函数,编译器将不会生成默认的对应函数;
- 如,在该例子中声明了构造函数,然后定义一个变量,会报 no matching function 的错误;
- 对于默认的拷贝构造的操作,
- nobj3初始化nameValue时,会调用string的拷贝构造,参数为nobj1.nameValue;
- nobj3初始化objectValue时,会将nobj1.objectValue每个bits拷贝到nobj3.objectValue;
- 对于默认拷贝赋值操作,行为和拷贝构造一样
template<typename T>
class NameObject {
public:
NameObject(const std::string& name, const T& value) : nameValue(name), objectValue(value) { }
private:
std::string nameValue;
T objectValue;
};
#include "item_05.h"
int main() {
NameObject<int> nobj;
NameObject<int> nobj1("name", 5);
NameObject<int> nobj2("nobj2", 10);
NameObject<int> nobj3(nobj1); // copy construct
nobj1 = nobj2; // copy assignment
return 0;
}
// main.cpp:9:21: error: no matching function for call to ‘NameObject<int>::NameObject()’
// 9 | NameObject<int> nobj;
3. 默认拷贝赋值函数只有生成代码合法的情况下才会被创建
- 如果成员变量是引用或者是const类型,
- 赋值过程会改变引用或者const变量的值,所以默认拷贝赋值函数不能被生成
- 引用的值定义的时候必须被初始化,且以后不能改变指向;
- 如果父类中的拷贝赋值是私有的,继承的子类也不会生成默认拷贝赋值函数
- (条款12会详细介绍)
template<typename T>
class NameObject {
public:
NameObject(std::string& name, const T& value) : nameValue(name), objectValue(value) { }
private:
std::string& nameValue;
const T objectValue;
};
#include "item_05.h"
int main() {
std::string name_obj1("obj1");
std::string name_obj2("obj2");
NameObject<int> nobj1(name_obj1, 5);
NameObject<int> nobj2(name_obj2, 10);
nobj1 = nobj2;
return 0;
}
/*
* 错误信息
main.cpp: In function ‘int main()’:
main.cpp:14:13: error: use of deleted function ‘NameObject<int>& NameObject<int>::operator=(const NameObject<int>&)’
14 | nobj1 = nobj2;
| ^~~~~
In file included from main.cpp:3:
item_05.h:18:7: note: ‘NameObject<int>& NameObject<int>::operator=(const NameObject<int>&)’ is implicitly deleted because the default definition would be ill-formed:
18 | class NameObject {
| ^~~~~~~~~~
item_05.h:18:7: error: non-static reference member ‘std::string& NameObject<int>::nameValue’, can’t use default assignment operator
item_05.h:18:7: error: non-static const member ‘const int NameObject<int>::objectValue’, can’t use default assignment operator
*/
Item06 若不想使用编译器自动生成的函数,就应该明确拒绝
场景:对于中介买的房子是独一无二的,为这样的资产提供副本毫无意义
- 版本1
- 将拷贝构造和拷贝赋值函数设置为私有
- 但如果成员函数和友元函数访问,不会出现编译问题,会出现链接错误
- 版本2
- 将报错提前到编译期
- 子类调用拷贝构造时会调用父类的拷贝构造,会被编译器拒绝
- 会造成多重继承(见条款40)
- 版本3
- C++11新特性,解决上述问题
#ifndef __ITEM_06_H__
#define __ITEM_06_H__
#include <string>
#include <iostream>
// 版本1
// class HomeForSale {
// public:
// HomeForSale() {}
// private:
// HomeForSale(const HomeForSale&);
// HomeForSale& operator=(const HomeForSale&);
// };
// 版本2
class Uncopyable {
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale : public Uncopyable {};
// 版本3 c++11新特性
class HomeForSale {
public:
HomeForSale() {}
HomeForSale(const HomeForSale&) = delete;
HomeForSale& operator=(const HomeForSale&) = delete;
};
#endif
/*
* 版本1 报错
main.cpp:20:22: error: ‘HomeForSale::HomeForSale(const HomeForSale&)’ is private within this context
20 | HomeForSale h3(h1);
| ^
In file included from main.cpp:4:
item_06.h:10:5: note: declared private here
10 | HomeForSale(const HomeForSale&);
| ^~~~~~~~~~~
*/
/*
* 版本2报错
main.cpp:20:22: error: use of deleted function ‘HomeForSale::HomeForSale(const HomeForSale&)’
20 | HomeForSale h3(h1);
| ^
In file included from main.cpp:4:
item_06.h:22:7: note: ‘HomeForSale::HomeForSale(const HomeForSale&)’ is implicitly deleted because the default definition would be ill-formed:
22 | class HomeForSale : public Uncopyable {};
| ^~~~~~~~~~~
item_06.h:22:7: error: ‘Uncopyable::Uncopyable(const Uncopyable&)’ is private within this context
item_06.h:19:5: note: declared private here
19 | Uncopyable(const Uncopyable&);
*/
/*
* 版本3报错
main.cpp:20:22: error: use of deleted function ‘HomeForSale::HomeForSale(const HomeForSale&)’
20 | HomeForSale h3(h1);
| ^
In file included from main.cpp:4:
item_06.h:27:5: note: declared here
27 | HomeForSale(const HomeForSale&) = delete;
*/
Item07 为多态基类声明virtual析构函数
虚函数:
- 允许子类中的实现定制化;
- 实现机制(写的不够详细):
- 每个带有虚函数的类有一个vtbl(函数指针组成的数组);
- 每个带有虚函数定义的对象,都包含一个vptr(虚表指针);
- 对象调用虚函数,实际上取决于vptr指向的vtbl;
- 包含了vptr意味着不在具有移植性,因为定义的对象大小会增加;
虚析构函数运作机制:
- 最深层的子类的析构函数最先被调用,然后一次调用其父类的析构函数
- 如果析构函数不是虚的,删除父类指针的时候会出现资源泄漏的行为
- 父类的析构被调用,子类的析构没有被调用,子类申请的内存没有销毁
class TimeerKeeper {
public:
TimeerKeeper() {}
virtual ~TimeerKeeper() {}
};
class AtomicClock : public TimeerKeeper {};
class WaterClock : public TimeerKeeper {};
TimeerKeeper* creatTimeKeeper() { return new TimeerKeeper(); }
int main() {
TimeerKeeper* ptk = creatTimeKeeper();
delete ptk;
return 0;
}
避免继承一些标准库的类,如string,stl中的容器等,避免出现子类未被析构的情况;
这些标准库的类,设计目的并非是为了多态;
class SpecialString : public std::string {};
std::string* ps = new SpecialString();
delete ps;
纯虚函数
- 将析构函数声明为纯虚函数,需要提供一份定义;
- 因为在派生类中会调用~AWOV(),如果没有定义,链接器会报未定义的错误;
class AWOV {
public:
virtual ~AWOV() = 0;
};
AWOV::~AWOV() {}
class TestAWOV : public AWOV {};
int main() {
TestAWOV t;
return 0;
}
/*
* 报错信息
/usr/bin/ld: /tmp/ccmxNHov.o: in function `TestAWOV::~TestAWOV()':
main.cpp:(.text._ZN8TestAWOVD2Ev[_ZN8TestAWOVD5Ev]+0x26): undefined reference to `AWOV::~AWOV()'
collect2: error: ld returned 1 exit status
*/
Item10 令operator=返回一个reference to *this
类在实现赋值操作符应该保持和内置类型同样的结构
-
一致性:返回引用到*this可以使赋值操作的语法与其他操作符一致,例如成员函数调用或算术运算符。这样可以提高代码的一致性和可读性。
-
可修改性:返回引用到*this使得赋值操作可以修改当前对象的状态。这对于某些特定的操作是非常有用的,例如自赋值检测和资源管理。
-
减少拷贝:通过返回引用,避免了不必要的对象拷贝操作,提高了性能和效率。特别是对于大型对象或需要频繁赋值的情况下,减少了内存和时间的开销
class Widget {
public:
Widget& operator=(const Widget& rhs) {
// 赋值操作
return *this;
}
};
int x, y, z;
x = y = z = 10;
Widget w1, w2, w3;
w1 = w2 = w3;