为何抽象类可作为容器类型的函数参数?模板何时实例化?
抽象类作为std::vector元素:编译通过的原因与模板实例化时机
先看问题中的代码:
#include <vector> #include <iostream> class Base{ public: virtual ~Base() = default; Base(const Base&) = default; Base(Base&&) = default; Base &operator=(const Base&) = default; Base &operator=(Base&&) = default; virtual void Method() = 0; int m_data{0}; }; class Derived : public Base{ public: void Method() override{} }; void func(std::vector<Base> &vec){ int var = vec[0].m_data;//this compiles //auto base = vec[0]; //this does not compile } int main(){}
一、为什么std::vector<Base>的函数声明能通过编译?
C++模板遵循延迟实例化规则:模板的具体代码不会在声明阶段生成,只有当代码实际需要使用模板的某个成员(或生成模板的具体实例)时,编译器才会进行实例化操作。
这段代码里,func只是声明了参数是std::vector<Base>&,此时编译器不需要生成std::vector<Base>的任何实现代码,自然不会去检查Base是否可实例化。抽象类的限制是不能创建其实例对象,但仅仅声明一个指向该类型容器的引用,完全不涉及对象创建,所以编译可以通过。
二、为什么vec[0].m_data能编译,而auto base = vec[0]不行?
vec[0]返回的是Base&(抽象类的引用),C++允许抽象类的引用和指针存在(这正是抽象类设计的核心目的之一:通过基类指针/引用实现多态调用)。访问引用指向对象的成员m_data,只是通过引用寻址读取内存,不需要创建新的Base对象,因此编译合法。auto base = vec[0]本质是执行拷贝构造:把vec[0](Base&)拷贝到新的Base对象base中。这时候必须创建Base的实例,但Base是抽象类(包含纯虚函数),C++明确禁止创建抽象类的对象,因此这行代码会触发编译错误——它强制编译器尝试实例化Base的拷贝构造函数,而这违反了抽象类的规则。
三、std::vector<Base>什么时候会被实例化?
当代码的操作需要编译器生成std::vector<Base>的具体实现代码时,模板就会被实例化,此时编译器会检查Base是否满足std::vector的元素要求(即可构造、可拷贝等),这时候就会因为Base是抽象类而报错。常见的触发场景包括:
- 创建
std::vector<Base>对象并执行需要实例化元素的操作:比如声明std::vector<Base> vec;后调用vec.resize(5)(需要默认构造Base对象)、vec.push_back(Derived{})(需要将Derived切片为Base,即拷贝构造Base),都会触发实例化并报错。 - 执行涉及创建
Base实例的容器操作:比如前面提到的auto base = vec[0],需要拷贝构造Base对象,此时会触发编译器检查Base是否可实例化。 - 实际调用
func函数且函数内操作依赖vector成员的实例化:比如如果func里写了vec.push_back(Derived{}),当func被调用时,编译器会实例化vector的push_back成员,进而检查Base是否可拷贝构造,最终报错。
内容的提问来源于stack exchange,提问作者orlandini
相关产品推荐
相关产品推荐

