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)就是一个类或一个函数,但是需要我们用一组类型或者值对其进行参数化。
  • 使用模板表示那些通用的概念,通过指定实参,生成特定的类型或者函数。
相关推荐
2301_789015621 小时前
C++:二叉搜索树
c语言·开发语言·数据结构·c++·算法·排序算法
leiming68 小时前
C++ vector容器
开发语言·c++·算法
apocelipes11 小时前
从源码角度解析C++20新特性如何简化线程超时取消
c++·性能优化·golang·并发·c++20·linux编程
ozyzo11 小时前
求1~n的累加和
c++
charlie11451419112 小时前
现代C++嵌入式教程:C++98基础特性:从C到C++的演进(1)
c语言·开发语言·c++·笔记·学习·教程
历程里程碑12 小时前
C++ 18智能指针:告别内存泄漏的利器
开发语言·c++
XFF不秃头13 小时前
力扣刷题笔记-全排列
c++·笔记·算法·leetcode
Code Warrior14 小时前
【C++】智能指针的使用及其原理
开发语言·c++
月光在发光14 小时前
多态(虚函数核心作用原理)--C++学习(0)
c++·学习
Sunsets_Red15 小时前
2025 FZYZ夏令营游记
java·c语言·c++·python·算法·c#