Ответ Тарека работает для общего случая, когда можно динамически использовать c выделение памяти (что обычно лучше, если sizeof(B)
значительно больше, чем sizeof(A)
). Я не пытаюсь конкурировать с этим ответом, а просто добавляю некоторые вопросы для обсуждения. Во многих (но далеко не во всех) проблемных областях считается плохой практикой dynamic_cast
вместо того, чтобы добавлять к A
a virtual void test() { }
- обратите внимание, что тело функции ничего не делает - тогда сделайте B
'* test
переопределение (т.е. void test() override { ...existing body...}
). Таким образом, l oop может просто сказать ptr->test()
, не заботясь о типе среды выполнения (то есть, действительно ли это B
объект). Этот подход имеет больше смысла, если операция "test
" имеет какой-то логический смысл для всей иерархии классов, но прямо сейчас в A
ничего не стоит проверять, особенно если вы добавляете производный тип C
из A
напрямую или через B
также потребуется функция test
, вызываемая из l oop: вам не нужно go для каждого такого l oop и добавлять дополнительный dynamic_cast<>
тест.
Просто для удовольствия альтернативы, которая оказывается ближе к вашему запросу на vector
, который может "может принимать B
структуры типа" (хотя уже не vector<A>
), вы можете получить почти те же результаты, используя std::variant
и иметь A
или B
, хранящиеся непосредственно в непрерывной памяти, управляемой vector
, что лучше всего работает, если есть небольшая разница в размере или использование памяти не Неважно, но объекты достаточно малы, чтобы локальность кэша ЦП была полезна для производительности.
#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 << "B\n"; }
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
, и, например, жесткий код a nullptr
возврат из любого dynamic_cast<B*>
(или бросок из dynamic_cast<B&>
). Вы можете подумать, что компилятору следует запретить не выполнять проверки во время выполнения, когда вы используете полиморфизм во время выполнения, но в действительности все наоборот: оптимизаторы компилятора очень стараются определить ситуации, когда тип во время выполнения известен, и виртуальной диспетчеризации можно избежать, так как это позволяет избежать некоторой косвенности и вызова функции вне строки и может позволить исключение мертвого кода (т. е. если test()
ничего не делает, не генерируйте никакого кода для
Другие практические проблемы с выборочной перезаписью A
объектов в vector
с B
s: - неправильный элемент-деструктор будет вызван при разрушении vector
- если кто-либо когда-либо добавлять элементы данных басов в B
, так что sizeof(B) > sizeof(A)
, размещение new
-ing объекта B
в vector
перезапишет память для следующего объекта (или с конца vector
) .
Что-то, что мне сказали, называется Идиома Виртуального конструктора Copeland , где тип объекта изменяется, например milar до operator new(&a) B{}
(хотя в этом случае это может быть сделано из конструктора operator new(this) B{}
), но вам нужно быть экспертом по языку и / или реализации, чтобы знать, безопасно ли / когда использовать.