访问存储在向量C++中的结构的多态成员

Accessing polymorphic members of a struct that is stored in a vector C++

本文关键字:结构 多态 成员 C++ 存储 向量 访问      更新时间:2023-10-16

我有一个结构a和结构B(B继承自a(。有没有一种方法可以创建std::vector,它具有模板类型a,但可以接受B类型的结构,并且当迭代通过时,我可以访问结构B专有的成员(显然要检查以确保它是B类型(。

您可以将对象存储为指针,并使用dynamic_cast检查下转换。

#include <iostream>
#include <memory>
#include <vector>
struct A {
virtual ~A() = default;
};
struct B : public A {
void test() { std::cout << "I'm B(" << this << ")" << std::endl; }
};
int main() {
std::vector<std::unique_ptr<A>> elements;
elements.push_back(std::make_unique<A>());
elements.push_back(std::make_unique<B>());
for (const auto &e : elements) {
if (auto ptr = dynamic_cast<B *>(e.get())) {
ptr->test();
}
}
return 0;
}

Tarek的答案适用于使用动态内存分配的常见情况(如果sizeof(B)明显大于sizeof(A),则通常更好(。我不想与这个答案竞争,只是想补充一些讨论点。在许多(但远非所有(问题域中,dynamic_cast被认为是一种糟糕的做法,而不是在A中添加virtual void test() { }——请注意,函数体什么都不做——然后使Btest成为覆盖(即void test() override { ...existing body...}(。这样,循环可以只说ptr->test(),而不关心运行时类型(即它是否真的是B对象(。如果"test"操作对类的整个继承结构具有某种逻辑意义,那么这种方法更有意义,但目前在A中没有什么值得测试的,特别是当您直接或通过B添加从A派生的C类型时,它还希望从循环中调用test函数:您不希望真的必须转到每个这样的循环并添加额外的dynamic_cast<>测试。

只是为了找到一个更接近您对vector的请求的替代方案,该CCD_19可以"接受B类型的结构">(尽管不再是vector<A>(,您可以使用std::variant获得大致相同的结果,并将AB直接存储在vector管理的连续内存中,如果大小差异很小或内存使用情况无关紧要,但对象足够小,CPU缓存位置对性能很有用,则效果最好。

#include <vector>
#include <iostream>
#include <variant>
struct A {
virtual void f() const { /* do nothing */ }
};
struct B : A {
int i_;
B(int i) : i_{i} { }
void f() const override { std::cout << "Bn"; }
void g() const { std::cout << "B::g() " << i_ << 'n'; }
};
int main()
{
std::vector<std::variant<A, B>> v{ A{}, A{}, B{2}, A{}, B{7}, B{-4}, A{} };
for (const auto& x : v)
if (const auto* p = std::get_if<B>(&x))
p->g();
}

另外,不能简单地使用vector<A>并用B对象覆盖某些元素的原因是,以这种方式向编译器撒谎会产生未定义的行为。关于为什么这可能被该语言禁止的示例,请考虑对于正常的代码生成,编译器应该能够依赖于vector只存储A类型对象的编译时知识,例如,对任何dynamic_cast<B*>nullptr返回(或从dynamic_cast<B&>抛出(进行硬编码。当你使用运行时多态性时,你可能会认为应该禁止编译器不进行运行时检查,但事实恰恰相反——编译器优化器会非常努力地识别运行时类型已知的情况,并且可以避免虚拟调度,因为这避免了一点间接性和越界的函数调用,并且可以允许死代码消除(即,如果test()什么都不做,则不生成用于对其的调用的任何代码(。

Bs选择性地重写vector中的A对象的其他实际问题:-当vector的析构函数被析构函数时,将调用错误的元素析构函数-如果有人将贝斯的数据成员添加到B中,使得sizeof(B) > sizeof(A),则将new放置到vector中将覆盖以下对象的内存(或vector的末尾(。

有一种东西被称为Copeland Virtual Constructor Idiom,其中对象的类型被更改为类似于operator new(&a) B{}(尽管在这种情况下,它可以从构造函数operator new(this) B{}中完成(,但你必须是一名语言和/或实现专家,才能知道它是否/何时可以安全使用。