为什么C++中不能声明 std::vector<AbstractClass>?

2025年2月11日 | 阅读 7 分钟

概述

C++底层标准模板库(STL)的几个主要组成部分之一是动态集合 std::vector,它可以容纳几乎任何类型的结构。它随后提供了一种易于修改且成功的方法来管理各种资源。std::vector 和抽象类(std::vector)的结合经常在开发者之间引起困惑。因此,有必要检查抽象类的特性及其与 STL 容器和 C++ 类型系统的交互,以理解为什么这种策略会产生问题。

在编程语言中,C++ 中的抽象方法,特别是任何拥有至少一个纯虚函数的类,即无法直接使用或实现的函数。通过为其他类提供基础,抽象类通过派生类促进了定制。

Why can't we declare a std::vector AbstractClass in C++?

std::vector 无法直接处理它们,因为抽象类不能被实例化,这导致了一个根本性的不一致。

为了解决这个限制,开发者在与 std::vector 配合使用抽象类类型时,通常会使用指针或智能指针。通过在 vector 中存储指向派生类的指针,而不是抽象基类本身,他们可以利用多态性来管理实际派生类对象的生命周期,同时规避抽象基类带来的问题。这种方法允许灵活地将抽象类与 STL 容器结合使用,为抽象基类带来的限制提供了一个实用的解决方案。

性质

我们为什么不能在 C++ 中声明 std::vector<AbstractClass> 的问题根源于抽象类的几个关键特性以及 STL 容器的要求。理解这些特性有助于阐明其中涉及的基本限制和挑战。

  1. 抽象类的性质: C++ 中的抽象类被定义为包含至少一个纯虚函数的类。这个特性意味着抽象类不能被直接实例化。它们旨在成为其他派生类的基类,这些派生类提供了抽象方法的具体实现。由于 std::vector 被设计用来存储可以被构造、复制和销毁的具体类型的实例,因此它不能直接处理抽象类。无法实例化抽象类意味着 std::vector 无法在其容器中管理或操作它们。
  2. 容器要求: std::vector 是 C++ STL 中的一个标准容器,它要求其元素类型可以进行默认构造、复制构造和析构。这些操作对于管理 std::vector 内部进行的动态重排和内存分配至关重要。抽象类不满足这些要求,而且它们是无法自行实例化的不完整类型。因此,std::vector 无法对抽象类执行这些操作,导致了根本性的不兼容。
  3. 多态性和对象管理: 使用抽象类的本质通常涉及多态性,其中操作在抽象基类的指针或引用上执行,而实际操作在运行时由派生类确定。要利用 std::vector 的多态性,我们通常会存储派生类对象的指针或智能指针,而不是抽象基类本身。这种方法允许 std::vector 管理指向完全具体类型的指针,同时仍然利用抽象基类提供的多态行为。
  4. 内存和类型安全: 在处理抽象类时,内存管理和确保类型安全变得更加复杂。尽管它们没有配备适当的特性和信息来成功地执行这些操作,但如果允许 std::vector 直接处理抽象类对象,它就无法保证适当的内存管理或类型保密性。通过使用指向具体实现的指针,可以将对象管理职责转移给派生类,从而减轻了上述问题。

示例

输出

 
DerivedClass1 doing something
DerivedClass2 doing something   

说明

考虑到它作为一个抽象的基类,通过一个名为 `doSomething()` 的完全虚函数创建,因此直接构造 `AbstractClass` 似乎是不可行的。任何派生类都必须按照设计来实现此特定函数。抽象类还具有一个虚析构函数,用于确保在使用基类指针删除对象时执行创建类的析构函数。

我们收集了两个派生类,`DerivedClass1` 和 `DerivedClass2`,它们都以独特的方式实现了 `doSomething()` 算法。这些实现仅仅生成不同的消息,以显示每个生成的类如何以不同的方式响应方法调用。

在 `main` 函数中,`std::vector` 存储指向派生类组件的 `std::unique_ptr` 指针。

使用 `std::unique_ptr` 可确保动态分配对象的自动且异常安全的管理,并在对象超出作用域时处理它们的析构。

`std::make_unique` 函数创建 `DerivedClass1` 和 `DerivedClass2` 的实例,并将它们包装在 `std::unique_ptr` 中,然后将它们推入 vector。这使得 vector 能够管理这些对象的生命周期,同时保持代码的整洁并防止内存泄漏。

复杂度

在 C++ 中尝试声明 `std::vector` 的操作是由抽象类的一些问题以及 `std::vector` 对其每个组件的规范所触发的。

抽象概念以及依赖实现

在 C++ 中,如果一个元素至少有一个纯虚函数(或通过将参数设置为 0 来声明的参数),则该元素可以被认为是抽象的。这个声明表明,虽然类定义了用户界面,但其行为尚未完全实现,因此无法直接创建。派生类必须提供这些纯虚函数的具体实现的约束对于面向对象编程中的抽象概念至关重要。

多态性和对象管理

为了处理抽象类的多态行为,通常的做法是使用指针或智能指针。这是因为指针可以引用派生类实例,从而允许 vector 通过这些指针间接存储和管理对象。通过在 vector 中存储指针,例如 `std::unique_ptr<AbstractClass>` 或 `std::shared_ptr<AbstractClass>`,我们回避了 vector 本身处理直接对象管理复杂性的需求。

C++ 中 `std::vector<AbstractClass>` 的限制

无法在 C++ 中声明 `std::vector<AbstractClass>` 的限制源于与抽象类以及 `std::vector` 所需操作相关的几个基本约束。以下是这些限制的详细介绍:

  1. 复制和移动语义: `std::vector` 依赖于复制和移动其元素的能力来管理动态重排和确保正确的对象管理。然而,抽象类无法以有意义的方式进行复制或移动,因为它们是缺乏完整实现的“不完整类型”。因此,尝试直接将抽象类存储在 vector 中将违反这些要求,从而可能导致元素管理和容器操作方面出现问题。
  2. 缺乏存储语义: 即使我们能够找到一种方法来直接存储抽象类,`std::vector` 也依赖于已完全定义的元素来分配空间和管理对象。由于抽象类不提供完整的对象定义,`std::vector` 无法为这些类型分配或处理存储。此限制会影响容器执行其基本操作的能力,例如重排和管理内部内存。
  3. 操作约束: 对于 vector 功能至关重要的赋值和比较等操作也假定元素是完全可操作的对象。抽象类不满足这些操作要求,因为它们并非旨在直接用于此类目的。缺乏完整的操作接口进一步加剧了在 `std::vector` 等容器中使用抽象类的难度。

结论

在 C++ 中,我们不能声明 `std::vector<AbstractClass>`,因为 `std::vector` 要求其元素是可复制和可移动的,而抽象类不满足这一约束。抽象类定义为至少有一个纯虚函数,因此不能直接实例化。这个特性本身使得 `std::vector` 无法管理此类元素的集合,因为使用抽象类进行复制和移动等 vector 操作将不可行。

总而言之,虽然由于类的抽象性质无法直接使用 `std::vector<AbstractClass>`,但通过使用指针、智能指针或替代容器设计,可以有效地管理和操作派生自抽象类的对象的集合。这些方法在遵守标准容器要求的同时,保持了多态性的灵活性和强大功能。