【发布时间】:2022-08-19 03:13:07
【问题描述】:
在std::vector<T> 中,向量拥有分配的存储空间,它构造Ts 并破坏Ts。不管T\ 的类层次结构如何,std::vector<T> 知道它只创建了一个T,因此当调用.pop_back() 时它只需要销毁一个T(不是T 的某个派生类)。采取以下代码:
#include <vector>
struct Bar {
virtual ~Bar() noexcept = default;
};
struct FooOpen : Bar {
int a;
};
struct FooFinal final : Bar {
int a;
};
void popEm(std::vector<FooOpen>& v) {
v.pop_back();
}
void popEm(std::vector<FooFinal>& v) {
v.pop_back();
}
https://godbolt.org/z/G5ceGe6rq
FooFinal 的 PopEm 只是将向量的大小减少 1(元素)。这是有道理的。但是PopEm for FooOpen 调用了类通过扩展Bar 得到的虚拟析构函数。鉴于FooOpen 不是最终的,如果在FooOpen* 指针上调用普通delete fooOpen,则需要执行虚拟析构函数,但对于std::vector,它知道它只生成了@987654341 @ 并且没有构造它的派生类。因此,std::vector<FooOpen> 不能将类视为 final 并省略对pop_back() 上的虚拟析构函数的调用吗?
-
不,编译器不知道向量只会包含 FooOpen。也许稍后它将与其他一些确实插入从 FooOpen 派生的类的组件链接。所以你的假设只对这个 sn-p 的代码有效。使用 FooFinal 可以完成优化。
-
@PepijnKramer 如何插入派生自
FooOpen的类?我能看到的唯一可能性是用户可以将派生对象放置到FooOpen元素的存储中,这至少取决于许多未指定的行为,但我觉得应该是库未定义的行为首先。 -
@PepijnKramer 我认为关键是即使我们有一百万个继承 fooopen 的类,它们中的任何一个都不能存储在向量中。向量总是构造和破坏 fooopens,仅此而已。对这个问题进行猜测:向量知道可以进行这种优化,但这并不意味着编译器知道。编译器必须进行一些复杂的代码分析才能弄清楚这一点。我对正在使用的优化技术知之甚少,但我想你需要对向量进行一些特殊处理才能实现这一点。
-
@Wutz 不需要复杂的分析。规范说
std::allocator_traits::destroy用于销毁元素,对于std::allocator作为分配器,这仅意味着(不合格的)析构函数调用。如果使用std::allocator,标准库可以检测容器并对其进行特殊处理(他们已经这样做以优化可复制的类型),然后始终使用合格的析构函数调用而不是allocator_traits::destroy,这将强制执行静态调度,即使该类有一个虚拟析构函数。 -
@UriRaz 不,如果您尝试将派生类插入向量中,您将只存储它的切片副本,因此也只会调用
FooOpen\ 的析构函数。这就是重点。不可能在向量中存储除FooOpen之外的任何其他类型。矢量接口根本不允许其他任何事情。
标签: c++ vector destructor final