抽象机制
1. 虚函数 使用关键字virtual 声明的函数,意思是可能随后在其派生类中重新定义。
纯虚函数 在声明的末尾使用=0 的函数,说明是纯虚函数。
抽象类 含有纯虚函数多的类称为抽象类(abstract class).
多态类型 如果一个类负责为其他一些类提供接口,前面一个类被称为多态类型。
虚函数是如何解析到正确的执行函数呢?
采用的是虚函数表vtbl:
一般编译器将虚函数的名字转换成函数指针表中对应的索引值
class Vector {
private:
double* elem;
int sz;
public:
Vector(int s) : elem{new double[s]}, sz{s} {
}
~Vector() {delete [] elem;};
double& operator[](int i) {return elem[i];}
int size() const {return sz;}
};
class EmplyClass {
};
class Container {
// Vector v;
public:
void function() {}
};
class VirtualContainer{
// Vector v;
public:
virtual ~VirtualContainer(){}
virtual void virtualFunction() = 0;
};
class SubContainer : public VirtualContainer {
public:
virtual ~SubContainer(){}
void virtualFunction() override {}
};
class SubContainer2 : public Container {
};
int main(int argc, char **argv) {
SubContainer con;
SubContainer2 con2;
EmplyClass c;
std::cout << "SubContainer size: " <<sizeof(con)/sizeof(char) << std::endl;
std::cout << "SubContainer2 size: " <<sizeof(con2)/sizeof(char) << std::endl;
std::cout << "EmplyClass size: " <<sizeof(c) << std::endl;
std::cout << "Version " << chapter3_VERSION_MAJOR << "." << chapter3_VERSION_MINOR << std::endl;
return 0;
}
运行结果为:
SubContainer size: 8
SubContainer2 size: 1
EmplyClass size: 1
Version 0.1
对于con(SubContainer) , 继承自类VirtualContainer(抽象类), 存在一个vtbl 指针,如下图:
在64位操作系统中指针占8个byte.
对于con2(SubContainer2), 继承自Container(非抽象类),其中没有, 如下图:
占用1个byte.
而对于一个空的类型EmptyClass 对象, 如下图:
占用1byte.
- What is empty class ?
Empty class: It is a class that does not contain any data members (e.g. int a, float b, char c, and string d, etc.) However, an empty class may contain member functions.
对于继承自非抽象类的子类和一个空实体类,占用1个byte. Why is the Size of an Empty Class Not Zero in C++?
When the structure was introduced in C, there was no concept of Objects at that time. So, according to the C standard, it was decided to keep the size of the empty structure to zero.
In C++, the Size of an empty structure/class is one byte as to call a function at least empty structure/class should have some size (minimum 1 byte is required ) i.e. one byte to make them distinguishable.
- 如果类包含虚函数,则该类的对象需要一个额外的指针;另外对于这样的类需要一个vtbl.
基类的析构函数
如果基类不提供析构函数,或者析构函数不是虚函数, 在使用抽象基类的接口操纵时,子类的析构函数会无法访问;
如果基类的析构函数是虚函数,则子类的析构函数会覆盖它。当使用基类的接口操纵派生类时,才能确保调用到正确的析构函数。
class VirtualContainer{
// Vector v;
public:
/*(1)*/
virtual ~VirtualContainer() {
std::cout << "VirtualContainer release: " << std::endl;
}
// virtual ~VirtualContainer(){}
virtual void virtualFunction() = 0;
};
class SubContainer : public VirtualContainer {
public:
~SubContainer() {
std::cout<< "SubContainner release" << std::endl;
}
// virtual ~SubContainer(){}
void virtualFunction() override {}
};
// 当以如下方式使用时:
VirtualContainer* baseContainer = new SubContainer();
delete baseContainer;
// 在此处析构 baseContainer 时, 如果没有(1) 的声明,~SubContainer() 将不会被调用
拷贝和移动
- 默认情况下,拷贝的默认含义是逐成员复制。 逐成员复制通常符合拷贝操作的本来语义,对于像vector 或者抽象类型 是不正确的。
拷贝容器
类对象的拷贝操作可以通过两个成员来定义:拷贝构造函数和拷贝复制函数
Vector(const Vector& a); // 拷贝构造函数
Vector& operator=(const Vector& a); // 拷贝运算符
Vector::Vector(const Vector& a)
:elem(new double[sz]),
sz(a.sz)
{
for (int i = 0; i < a.sz; i++) {
elem[i] = a.elem[i];
}
}
Vector& Vector::operator=(const Vector& a) {
double *p = new double[a.sz];
for (int i = 0; i < a.sz; i++) {
p[i] = a.elem[i];
}
delete[] elem;
elem = p;
sz = a.sz;
return *this;
}
移动容器
- 对于大的容器而言,拷贝过程耗费巨大(可能是时间或者空间上的耗费)
声明:
/** 移动构造函数*/
Vector(Vector&& a);
/** 移动赋值运算符*/
Vector& operator=(Vector&& a);
实现:
Vector::Vector(Vector&& a)
:elem(a.elem),
sz(a.sz)
{
std::cout << "移动构造函数被调用" << std::endl;
a.elem = nullptr;
a.sz = 0;
}
Vector& Vector::operator=(Vector&& a) {
std::cout << "移动赋值运算符被调用" << std::endl;
if (this != &a) {
this->elem = a.elem;
this->sz = a.sz;
}
a.elem = nullptr;
a.sz = 0;
return *this;
}
- 左值: 能出现在赋值运算符的左侧的内容
- 右值: 无法为其赋值的值
- && 右值引用
- 移动构造函数不接受(const实参, 因为移动构造函数会删除实参中的值,如果放了const 则无法删除)
- 使用移动构造函数或拷贝构造的场合
资源管理
- 可以把指针转化为资源句柄,比如使用智能指针(如unique_ptr)实现强资源安全,对于一般概念上的资源可以消除资源泄漏。
抑制操作
虽然类中可能未定义拷贝构造或者移动构造函数等,但是当代码中存在拷贝或者移动的操作情况下,编译器会自动生成相应的构造函数或者赋值函数。
对于部分实现类,使用默认的拷贝构造函数或移动操作常常意味着风险。对于不需要或者不能移动或者复制的类,最好是删除默认的拷贝和移动操作。
形式大致如下:
Shape(const Shape&) = delete; // 没有拷贝操作
Shape& operator=(const Shape&) = delete;
Shape(Shape&& ) = delete; // 没有移动操作
Shape& operator=(Shape&& )= delete;
模板
- 模板: 一个模板(template)就是一个类或一个函数,但是需要我们用一组类型或者值对其进行参数化。
- 使用模板表示那些通用的概念,通过指定实参,生成特定的类型或者函数。