Я изо всех сил пытаюсь выразить список, содержащий различные экземпляры производных классов в объекте-контейнере, в соответствии с общим интерфейсом. Добавление их к вектору дает возможность использовать базовый класс, указатели и ссылки. Приведение их к конкретным типам с помощью дискриминатора позже оказывается проблематичным.
В 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++?
(Обратите внимание, это всего лишь псевдокод, проблема возникла в корявом анализаторе сообщений на уровне байтов, где на каждый тип сообщения приходится один класс. Двух достаточно, чтобы найти закономерность.)
Вероятно, это я возвращаюсь к C++ после 20 лет C#, где доступ к указателям охраняется директивой компилятора «небезопасно». Я все еще приспосабливаюсь.
Как вы думаете, подойдет ли vector<unique_ptr<BaseProduct>>? Разрешит ли это reinterpret_cast и будет ли это приемлемым с точки зрения защиты от ошибок?
эквивалент 'as' С# - dynamic_cast в С++
да, вектор unique_ptr - это путь, и вам не нужно поле типа
Я могу работать с этим как с определением. Спасибо.
std::vector<BaseObject>
содержит только BaseObject
s. Он не может содержать какой-либо другой тип, независимо от того, наследуется ли он от 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 Да. Также гарантируется, что подсчет ссылок будет потокобезопасным, а эта потокобезопасность налагает дополнительные накладные расходы.
Эта ссылка может помочь другим найти пример этой работы — см. Пример 4 на этой странице: Learn.Microsoft.com/en-us/cpp/cpp/…
unique_ptr не зло, shared_ptr не зло