Доступ к полиморфным c членам структуры, которая хранится в векторе C ++ - PullRequest
1 голос
/ 23 февраля 2020

У меня есть структура A и структура B (B наследуется от A). Есть ли способ создать std :: vector, который имеет шаблон типа A, но может принимать структуры типа B, и при повторном прохождении я могу получить доступ к членам, исключающим структуру B (очевидно, проверяя, чтобы убедиться, что это тип B).

Ответы [ 2 ]

2 голосов
/ 23 февраля 2020

Вы можете хранить свои объекты как указатели и использовать 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;
}
1 голос
/ 26 февраля 2020

Ответ Тарека работает для общего случая, когда можно динамически использовать 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{}), но вам нужно быть экспертом по языку и / или реализации, чтобы знать, безопасно ли / когда использовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...