C++ 抽象机制

抽象机制

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)就是一个类或一个函数,但是需要我们用一组类型或者值对其进行参数化。
  • 使用模板表示那些通用的概念,通过指定实参,生成特定的类型或者函数。

相关推荐

  1. C++ 抽象

    2024-05-02 09:18:01       37 阅读
  2. C# 抽象&接口

    2024-05-02 09:18:01       54 阅读
  3. c# 抽象

    2024-05-02 09:18:01       55 阅读
  4. C# 抽象类、接口

    2024-05-02 09:18:01       39 阅读
  5. C# 抽象

    2024-05-02 09:18:01       37 阅读
  6. C++知识 抽象基类

    2024-05-02 09:18:01       58 阅读
  7. C# 抽象类与接口

    2024-05-02 09:18:01       64 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-02 09:18:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-02 09:18:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-05-02 09:18:01       87 阅读
  4. Python语言-面向对象

    2024-05-02 09:18:01       96 阅读

热门阅读

  1. springboot基础

    2024-05-02 09:18:01       31 阅读
  2. 5-用户权限控制(前端)

    2024-05-02 09:18:01       29 阅读
  3. 我选择哪个区块链平台将作品转化为NFT最合适?

    2024-05-02 09:18:01       35 阅读
  4. 设计模式-03 设计模式-依赖倒转原则案例分析

    2024-05-02 09:18:01       31 阅读
  5. Spring-MVC从入门到精通

    2024-05-02 09:18:01       23 阅读
  6. CAN测试工具

    2024-05-02 09:18:01       33 阅读
  7. CKEditor编辑器的简单使用方法,取值,赋值

    2024-05-02 09:18:01       29 阅读
  8. Github 2024-05-01 开源项目日报Top10

    2024-05-02 09:18:01       33 阅读
  9. map和set

    map和set

    2024-05-02 09:18:01      28 阅读
  10. 如何在Dlib库中实现目标跟踪

    2024-05-02 09:18:01       29 阅读
  11. C\#工控行业中的视觉技术栈

    2024-05-02 09:18:01       35 阅读
  12. 【Unity Consoler Redirection】Unity Log 跳转重定向

    2024-05-02 09:18:01       30 阅读
  13. golang netpoller揭秘

    2024-05-02 09:18:01       23 阅读