C++多态总结

多态(Polymorphism) 多态是允许不同类的对象使用相同的接口名字,但具有不同实现的特性。

在 C++ 中,多态主要通过虚函数(Virtual Function)和抽象基类(Abstract Base Class)来实现。

虚函数允许在派生类中重写基类的方法,而抽象基类包含至少一个纯虚函数(Pure Virtual Function),不能被实例化,只能作为其他派生类的基类。

通过多态,我们可以编写更加通用、可扩展的代码,提高代码的灵活性。

函数签名

函数签名由函数名、参数列表构成,不包含返回值。

虚函数

  • C++使用虚函数的时候,子类也要使用virtual关键字吗

c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此,在子类从新声明该虚函数时,可以加,也可以不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。

对虚函数的探究。

写了一段代码。

class A {
 public:
  void m1() { LOG_INFO("A"); }
};

class B : public A {
 public:
  void m1() { LOG_INFO("B"); }
};

TEST(TEST_POLY, NOT_VIRTUAL) {
  B b;
  A* ap = &b;
  ap->m1();
  B* bp = &b;
  bp->m1();
}

在想如果不加virtual,不也能重写吗?

但是输出结果

A
B

但是

class A {
 public:
  virtual void m1() { LOG_INFO("A"); }
};

class B : public A {
 public:
  void m1() override { LOG_INFO("B"); }
};

TEST(TEST_POLY, NOT_VIRTUAL) {
  B b;
  A* ap = &b;
  ap->m1();
  B* bp = &b;
  bp->m1();
}

输出

B
B

多态的析构函数必须是虚函数

经过上面的分析,就理解这个了。

为A和B添加析构函数

class A {
 public:
  virtual void m1() { LOG_INFO("A"); }
  ~A() { LOG_INFO("Delete A"); }
};

class B : public A {
 public:
  void m1() override { LOG_INFO("B"); }
  ~B() { LOG_INFO("Delete B"); }
};

然后创建两个B对象,分别用A和B的指针指向。

TEST(TEST_POLY, DESTORY) {
  A* ap = new B();
  delete ap;
  LOG_INFO("---");
  B* bp = new B();
  delete bp;
}

输出结果

2023-11-06 17:33:48 [F:/LeetCode/solution/lab/polymorphism.cpp:13:~A] INFO  - Delete A
2023-11-06 17:33:48 [F:/LeetCode/solution/lab/polymorphism.cpp:33:TestBody] INFO  - ---
2023-11-06 17:33:48 [F:/LeetCode/solution/lab/polymorphism.cpp:19:~B] INFO  - Delete B
2023-11-06 17:33:48 [F:/LeetCode/solution/lab/polymorphism.cpp:13:~A] INFO  - Delete A

如果用A指针指向B,并且析构函数不是虚函数,析构只会调用A的析构函数,而不会调用这个类实际的析构函数~B()。而析构B时,会先调用B的析构函数,再析构基类(A)。

而如果析构函数为虚函数

class A {
 public:
  virtual void m1() { LOG_INFO("A"); }
  virtual ~A() { LOG_INFO("Delete A"); }
};

class B : public A {
 public:
  void m1() override { LOG_INFO("B"); }
  ~B() override { LOG_INFO("Delete B"); }
};

TEST(TEST_POLY, DESTORY) {
  A* ap = new B();
  delete ap;
  LOG_INFO("---");
  B* bp = new B();
  delete bp;
}

输出

2023-11-06 17:35:27 [F:/LeetCode/solution/lab/polymorphism.cpp:19:~B] INFO  - Delete B
2023-11-06 17:35:27 [F:/LeetCode/solution/lab/polymorphism.cpp:13:~A] INFO  - Delete A
2023-11-06 17:35:27 [F:/LeetCode/solution/lab/polymorphism.cpp:33:TestBody] INFO  - ---
2023-11-06 17:35:27 [F:/LeetCode/solution/lab/polymorphism.cpp:19:~B] INFO  - Delete B
2023-11-06 17:35:27 [F:/LeetCode/solution/lab/polymorphism.cpp:13:~A] INFO  - Delete A

为什么要优先调用虚继承?

TODO

为什么不能用虚函数为模板函数

例如

class C {
 public:
  template <typename T>
  virtual void func();
};

报错'virtual' cannot be specified on member function templates。(注意模板函数中是能用虚函数的,但是虚函数不能有模板)

class C {
 public:
  template <typename T>
  virtual void func(){};

};
TEST(TEST_POLY, VIRTUAL) {
  C c;
  c.func<int>();
  c.func<long>();
}

比如上面的情况,因为虚函数表是一一对应的关系,

模板实例化

实例化函数:

void foo(T value) {}
template void foo<int>(int);  // 实例化 foo<int>

实例化类:

template <typename T>
class C0 {};
template class C0<int>;

实例化模板类中的模板函数:

template <typename T>
class C {
 public:
  template <typename U>
  void foo(){};
};
// 显式实例化模板类中的模板函数
template void C<int>::foo<long>();

模板实例化都是template,然后跟限定符,参数列表。

比如

template<class T1>
class Tmp1 {
  public:
  template<class T2>
  class Tmp2 {

  };
};
template class Tmp1<A>::Tmp2<B>;

template class Tmp1<A>::Tmp2<B>;中,第一个class指的是要实例化Tmp2这个类,这个类的标识符是 Tmp1<A>::Tmp2,并在后面给出参数B

空类的大小

想到很多好玩的:

  1. 空类的大小
  2. 继承空类的空类大小
  3. 继承空类,但是有成员变量的大小。
  4. 继承空类且有一个虚函数的类大小
  5. 空类,且有一个普通函数
class Empty {};
template <class E>
class Empty2 {
 public:
  E e;
};
class Empty4 : Empty {
  virtual void func(){};
};
TEST(TEST_EMPTY, SAMPLE) {
  Empty e1;
  LOG_INFO("%zu", sizeof(e1));
  Empty2<Empty> e2{};
  LOG_INFO("%zu", sizeof(e2));
  Empty2<int> e3{};
  LOG_INFO("%zu", sizeof(e3));
  Empty4 e4;
  LOG_INFO("%zu", sizeof(e4));
  Empty5 e5;
  LOG_INFO("%zu", sizeof(e5));
}

输出

1
1
4
8
1

为什么空类有大小呢?

假如内存栈上面有两个连续的空类,为了区分这两个类,不能让它们有相同的地址。

例如

TEST(TEST_EMPTY, SAMPLE2) {
  Empty e1;
  LOG_INFO("%p", &e1);
  Empty e2;
  LOG_INFO("%p", &e2);
}

输出

2023-11-07 00:11:36 [F:/LeetCode/solution/lab/polymorphism.cpp:72:TestBody] INFO  - 0000000000cdf88f
2023-11-07 00:11:36 [F:/LeetCode/solution/lab/polymorphism.cpp:74:TestBody] INFO  - 0000000000cdf88e

可以看到,两个类实例在栈上且相差1(地址从高到低)

如果在heap上就不太一样。

Empty e1;
Empty e2;
TEST(TEST_EMPTY, SAMPLE2) {
  LOG_INFO("%p", &e1);
  LOG_INFO("%p", &e2);
}

2023-11-07 00:13:35 [F:/LeetCode/solution/lab/polymorphism.cpp:72:TestBody] INFO  - 00000000005b6050
2023-11-07 00:13:35 [F:/LeetCode/solution/lab/polymorphism.cpp:73:TestBody] INFO  - 00000000005b6051

一个地址高,一个地址低。

参考Empty Class(空类)的作用_python 空class-CSDN博客, 空类可以很优雅地实现一些概念、约束。

Licensed under CC BY-NC-SA 4.0