Поиск производного объекта в векторе базы

Я нашел следующий код в каком-то проекте:

std::vector<Base*> objs_;

template <class TDerived>
T* Get() {
  auto objIt = std::find_if (objs_.cbegin(), objs_.cend(), [](Base* it) {
    return typeid(TDerived).name() == typeid(*it).name();
  });
  return objIt == objs_.cend() ? nullptr : *objIt;
}

Я пытаюсь выяснить, является ли использование typeid хорошим выбором? Потому что в C++ есть dynamic_cast, например:

template <class TDerived>
T* Get() {
  auto objIt = std::find_if (objs_.cbegin(), objs_.cend(), [](Base* it) {
    return dynamic_cast<T*>(it) != nullptr;
  });
  return objIt == objs_.cend() ? nullptr : *objIt;
}

Я знаю, что между приведенными выше решениями есть семантическая разница, потому что dynamic_cast также будет жаловаться на TDerived наследника, но это нормально, все используемые TDerived являются final.

Является ли typeid хорошим выбором, dynamic_cast лучше или есть лучшее решение?

typeid не обязательно должен быть уникальным, поэтому вряд ли это хороший выбор. С другой стороны, зачем вам искать точный тип? Не может ли виртуальная функция в Base быть более полезной?
BoP 09.04.2023 17:54

Нет, typeid не лучший выбор. Ни dynamic_cast. Весь этот подход следует выбросить и переписать с нуля, используя unique_ptr или shared_ptr и виртуальные методы. Избегайте написания кода, похожего на то, что было найдено в «каком-то проекте».

Sam Varshavchik 09.04.2023 17:54

Очевидный вопрос: зачем коду идентифицировать объекты по их типу? Как говорит @BoP, этого обычно легко избежать (и, следовательно, должно быть).

Paul Sanders 09.04.2023 19:50

Это не мой код, поэтому я его и спросил. Насчет "зачем искать точный тип" попробую сказать то, что понял из кода. Автор добавляет в коллекцию некоторые производные объекты, а затем проверяет, содержит ли она объект какого-либо типа. Если да, то он вызывает какую-то виртуальную функцию, иначе не вызывает.

excommunicado 09.04.2023 21:57

@excommunicado Тогда можно легко избежать теста «какой тип этого объекта». Просто добавьте в базовый класс виртуальную функцию «ничего не делать» и вызывайте ее независимо. Я указываю на это, чтобы продемонстрировать свое утверждение о том, что проверка типа (полиморфного) объекта редко, если вообще необходима.

Paul Sanders 09.04.2023 23:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
87
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Нет, typeid вообще не очень хорошая идея, потому что подтипирование одного из задействованных типов потребует обогащения частей, где проверяется typeid. Это противоречит принципу открытости/закрытости.

Кстати, с typeid есть много тонких проблем, например. нет стандартизации возвращаемых имен типов, и, кроме того, как указано в cppreference:

Нет никакой гарантии, что на один и тот же экземпляр std::type_info будут ссылаться все оценки выражения typeid для одного и того же типа, хотя они будут сравниваться равными, std::type_info::hash_code этих объектов type_info будет идентичным, поскольку будет их std::type_index.

Использование dynamic_cast немного лучше, потому что это может позволить соблюдать принцип открытости/закрытости, если вы не добавляете новый тип. Но если вы это сделаете, вы снова обязаны обогатить код. Более того, dynamic_cast требует, чтобы используемый класс/функция много знала об используемых классах. Это может ослабить инкапсуляцию и создать скрытую связь (т. е. вы больше не можете изменять используемые классы по своему усмотрению, потому что вы нарушаете некоторые предположения).

Лучший подход — переписать код полиморфным образом. Основные принципы C++ напоминают в связи с этим, что виртуальные функции следует предпочитать приведению типов . В более общем плане подход должен использовать принцип «говори, не спрашивай» и позволять полиморфному коду делать то, что он должен делать. Или выберите шаблон посетителя.

Вы не можете сравнивать имена, потому что они не должны иметь одинаковые значения указателя для одних и тех же типов. Например, typeid(TDerived).name() == typeid(TDerived).name() может быть ложным.

Вместо этого вы должны сравнивать typeid напрямую с std::type_info::operator==:

    return typeid(TDerived) == typeid(*it);

Что касается того, лучше ли это, чем dynamic_cast, это зависит. Для окончательного класса, как вы упомянули, семантически нет никакой разницы. Я думаю, что сравнение typeid делает ваши намерения более ясными.

Другие вопросы по теме