Как выразить список объектов с общим интерфейсом в C++

Я изо всех сил пытаюсь выразить список, содержащий различные экземпляры производных классов в объекте-контейнере, в соответствии с общим интерфейсом. Добавление их к вектору дает возможность использовать базовый класс, указатели и ссылки. Приведение их к конкретным типам с помощью дискриминатора позже оказывается проблематичным.

В C#, плюс-минус, я бы выразил это так:

public interface IOrderable 
{
  decimal GetUnitPrice( );
}
public class BaseProduct 
{
  public Guid Id {get;set;}
  public string Name {get;set;}
  public decimal UnitPrice {get;set;}
  //
  public GetUnitPrice( return UnitPrice; }
}
public class Digital : BaseProduct, IOrderable
{
  public int Bytes {get;set;}
}
public class Physical : BaseProduct, IOrderable
{
  public int MassInGrammes {get;set}
}
public class Order
{
  public List<IOrderable> listOfItems;
  public int TotalBytes() 
  { 
     int t = 0;
     foreach( var i in listOfItems )
     { 
        var d = i as Digital;          // <-- in C# this patterny works fine 
        if ( d != null ) t = t + d.Bytes;
     }
     return t;
  }
  public decimal TotalMass() { ... }
}

Когда я пытаюсь добиться этого на C++, я начинаю с базового класса и создаю вектор.

class BaseProduct
{
public:
  string type;    // <-- discriminator
  etc.
}
class Order
{
public:
  vector<BaseProduct> listOfItems;
  int totalBytes() {
    int t=0;
    for( BaseProduct bp: listOfItems )
    {
      if ( bp.type == "digital" )
       ... and this is where the wheels come off
           how to go from BaseProuct to Digital?
      if ( bp.type == "physical" )
       ... etc.
    }
    return t;
  }
}

Если я создам список на основе указателей, я смогу увидеть, как переинтерпретировать_приведение к известному производному типу. Это мое лучшее предположение.

Мне не удалось заставить работать версию со ссылками на объекты - определение вектора - самое близкое было wrap_reference<object&>.

Я чувствую, что делаю из этого тяжелую погоду, в основном из-за того, что С++ сосредоточен на владении/сроке жизни объекта, а также из-за общего совета о том, что указатели в чем-то злы, даже в современном С++. (Как все изменилось! C++ БЫЛ когда-то указателем!)

Что же тогда является хорошим (не обязательно лучшим) шаблоном для такого списка смешанного типа в C++?

(Обратите внимание, это всего лишь псевдокод, проблема возникла в корявом анализаторе сообщений на уровне байтов, где на каждый тип сообщения приходится один класс. Двух достаточно, чтобы найти закономерность.)

unique_ptr не зло, shared_ptr не зло

pm100 03.04.2023 02:34

Вероятно, это я возвращаюсь к C++ после 20 лет C#, где доступ к указателям охраняется директивой компилятора «небезопасно». Я все еще приспосабливаюсь.

S9DD 03.04.2023 02:44

Как вы думаете, подойдет ли vector<unique_ptr<BaseProduct>>? Разрешит ли это reinterpret_cast и будет ли это приемлемым с точки зрения защиты от ошибок?

S9DD 03.04.2023 02:46

эквивалент 'as' С# - dynamic_cast в С++

pm100 03.04.2023 02:47

да, вектор unique_ptr - это путь, и вам не нужно поле типа

pm100 03.04.2023 02:49

Я могу работать с этим как с определением. Спасибо.

S9DD 03.04.2023 02:50
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

std::vector<BaseObject> содержит только BaseObjects. Он не может содержать какой-либо другой тип, независимо от того, наследуется ли он от BaseObject или нет.

Помните, что в C++ переменные не являются указателями или ссылками, если только вы не объявите их таковыми. Это отличается от C#, где все переменные типов классов являются ссылками на динамически выделяемые объекты.

Чтобы иметь полиморфизм в C++, вы должны использовать (умные) указатели или ссылки. Чтобы заполнить контейнер динамически размещаемыми объектами типов, производных от BaseObject, вам, скорее всего, понадобится либо std::vector<std::unique_ptr<BaseObject>>, либо std::vector<std::shared_ptr<BaseObject>>.

Какой из них вы хотите, зависит от ваших потребностей:

  • std::unique_ptr практически не имеет накладных расходов времени выполнения по сравнению с необработанным указателем, но ограничен уникальной семантикой владения.
  • std::shared_ptr имеет семантику совместного владения, более похожую на ссылки C# для сбора мусора, но влечет за собой дополнительные накладные расходы во время выполнения.

Спасибо за понимание объявлений классов C# и C++ — полезное напоминание. Очень привык к тому, что классы С# являются ссылками в коде. Предполагается, что дополнительные накладные расходы с shared_ptr связаны с подсчетом ссылок?

S9DD 03.04.2023 03:05

@ S9DD Да. Также гарантируется, что подсчет ссылок будет потокобезопасным, а эта потокобезопасность налагает дополнительные накладные расходы.

Miles Budnek 03.04.2023 03:28

Эта ссылка может помочь другим найти пример этой работы — см. Пример 4 на этой странице: Learn.Microsoft.com/en-us/cpp/cpp/…

S9DD 03.04.2023 03:32

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