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)就是一个类或一个函数,但是需要我们用一组类型或者值对其进行参数化。
  • 使用模板表示那些通用的概念,通过指定实参,生成特定的类型或者函数。
相关推荐
lsnm1 小时前
C++新手项目-JsonRPC框架
开发语言·c++·1024程序员节
给大佬递杯卡布奇诺2 小时前
FFmpeg 基本数据结构 AVPacket分析
数据结构·c++·ffmpeg·音视频
南方的狮子先生2 小时前
【数据结构】从线性表到排序算法详解
开发语言·数据结构·c++·算法·排序算法·1024程序员节
程序猿编码2 小时前
Linux 文件变动监控工具:原理、设计与实用指南(C/C++代码实现)
linux·c语言·c++·深度学习·inotify
好好学习啊天天向上3 小时前
多维c++ vector, vector<pair<int,int>>, vector<vector<pair<int,int>>>示例
开发语言·c++·算法
我狸才不是赔钱货3 小时前
CUDA:通往大规模并行计算的桥梁
c++·人工智能·pytorch
winds~4 小时前
【GUI】本地电脑弹出远程服务器的软件GUI界面
运维·服务器·c++
杨筱毅6 小时前
【穿越Effective C++】条款8:别让异常逃离析构函数——C++异常安全的关键支柱
c++·effective c++
code monkey.6 小时前
【探寻C++之旅】C++ 智能指针完全指南:从原理到实战,彻底告别内存泄漏
c++·c++11·智能指针