C++ 抽象机制

抽象机制

1. 虚函数 使用关键字virtual 声明的函数,意思是可能随后在其派生类中重新定义。

纯虚函数 在声明的末尾使用=0 的函数,说明是纯虚函数。

抽象类 含有纯虚函数多的类称为抽象类(abstract class).

多态类型 如果一个类负责为其他一些类提供接口,前面一个类被称为多态类型。

虚函数是如何解析到正确的执行函数呢?

采用的是虚函数表vtbl:

一般编译器将虚函数的名字转换成函数指针表中对应的索引值

CPP 复制代码
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;
}

运行结果为:

cpp 复制代码
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.

基类的析构函数

如果基类不提供析构函数,或者析构函数不是虚函数, 在使用抽象基类的接口操纵时,子类的析构函数会无法访问;

如果基类的析构函数是虚函数,则子类的析构函数会覆盖它。当使用基类的接口操纵派生类时,才能确保调用到正确的析构函数。

CPP 复制代码
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 或者抽象类型 是不正确的。

拷贝容器

类对象的拷贝操作可以通过两个成员来定义:拷贝构造函数和拷贝复制函数

CPP 复制代码
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;
}

移动容器

  • 对于大的容器而言,拷贝过程耗费巨大(可能是时间或者空间上的耗费)
CPP 复制代码
 声明:
	/** 移动构造函数*/
	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)实现强资源安全,对于一般概念上的资源可以消除资源泄漏。

抑制操作

虽然类中可能未定义拷贝构造或者移动构造函数等,但是当代码中存在拷贝或者移动的操作情况下,编译器会自动生成相应的构造函数或者赋值函数。

对于部分实现类,使用默认的拷贝构造函数或移动操作常常意味着风险。对于不需要或者不能移动或者复制的类,最好是删除默认的拷贝和移动操作。

形式大致如下:

CPP 复制代码
Shape(const Shape&) = delete;  // 没有拷贝操作
Shape& operator=(const Shape&) = delete;

Shape(Shape&& ) = delete;  // 没有移动操作
Shape& operator=(Shape&& )= delete;

模板

  • 模板: 一个模板(template)就是一个类或一个函数,但是需要我们用一组类型或者值对其进行参数化。
  • 使用模板表示那些通用的概念,通过指定实参,生成特定的类型或者函数。
相关推荐
笑鸿的学习笔记12 分钟前
qt-C++语法笔记之Qt Graphics View 框架中的类型辨析完全指南
c++·笔记·qt
山居秋暝LS12 分钟前
安装C++版opencv和opencv_contrib
开发语言·c++·opencv
谭欣辰1 小时前
LCS(最长公共子序列)详解
开发语言·c++·算法
Cando学算法1 小时前
鸽笼原理(抽屉原理)
c++·算法·学习方法
郝学胜-神的一滴2 小时前
跨平台动态库与头文件:从原理到命名的深度解析
linux·c++·程序人生·unix·cmake
代码中介商2 小时前
C++ 仿函数(Functor)深度解析:从基础到应用
开发语言·c++
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之字符串 --【字符串基础】:[NOIP 2018 普及组] 标题统计
c++·字符串·csp·高频考点·信奥赛·专项训练·标题统计
冯诺依曼的锦鲤3 小时前
从零实现高并发内存池:TCMalloc 核心架构拆解
c++·学习·算法·架构
无忧.芙桃3 小时前
C++IO库的超详细讲解
开发语言·c++
爱看书的小沐4 小时前
【小沐学GIS】基于C++渲染三维飞行仿真Flight Simulation(OpenGL )第十三期
c++·qt·webgl·opengl·飞行仿真·flight