std :: вариант классов с одинаковым базовым классом - PullRequest
0 голосов
/ 06 ноября 2018

Я не полностью понимаю использование вариантов, может быть, кто-то может понять, что я делаю неправильно (возможно, мой подход).

Пусть вариантный объект будет _Types двух классов, оба наследуют один и тот же класс.

class base
{
public:
    int foo;
};
class a: public base
{
    int bar;
};
class b: public base
{
    float bar;
};

byte variant_id = 0; // 0 for class A, 1 for class B
std::variant< base, a, b > variant;

Вот как я использую вариант:

void func( )
{
    auto& _variant = std::get< base >( variant ); // ideally would be = variant_id ? std::get< b >( variant ) : std::get< a >( variant )
    _variant.foo = 20;

    if ( variant_id == 1 )
    {
        auto& variant_ = std::get< b >( variant );
        variant_.bar = 20.f;
    }
    else
    {
        auto& variant_ = std::get< a >( variant );
        variant_.bar = 20;
    }

Может быть, союз более эффективен?

union
{
    a _a;
    b _b;
} variant;
byte variant_id = 0;

Ответы [ 4 ]

0 голосов
/ 06 ноября 2018

Старайтесь не запрашивать вариант для того, какой тип он содержит. Если вы это сделаете, ваш код по сути эквивалентен группе динамических приведений в цепочке if, запах кода.

Вместо этого позвольте варианту отправить за вас. И если вы хотите получить доступ к общим base из a и b, вам не нужен член base в этом варианте.

Использовать посетителя

std::variant< a, b > var;
void func( )
{
   std::visit([](auto&& v) {
     v.foo = 20; // Both `a` and `b` have a foo, this is well formed.
     v.bar = 20; // Both have a `bar` that can be assigned a 20. This is well formed too
   }, var);
}
0 голосов
/ 06 ноября 2018

Variant знает, какой тип он хранит. Union ожидает, что вы отслеживаете этот тип извне. Если вы попытаетесь получить доступ к неправильному элементу в variant, вы получите exception или nullptr, для Union это просто неопределенное поведение. ( ссылка )

0 голосов
/ 06 ноября 2018

Келер дал хороший ответ на некоторые технические ошибки в вашем использовании, но я чувствую, что вариант - неподходящий инструмент для работы здесь.

Как правило, вы бы использовали std::variant для несвязанных типов данных. Есть ли причина использовать вариант здесь? Поскольку у вас есть только подклассы base, вы обычно выбираете std::unique_ptr<base> или std::shared_ptr<base> (в зависимости от требований) и с этим покончите.

Единственная причина, по которой я хотел бы использовать вариант подклассов, заключается в том, чтобы гарантировать, что они могут храниться непрерывно, чтобы уменьшить затраты на память / косвенные обращения. И даже тогда я бы использовал его через интерфейс базового класса, вот так:

base& getBase(std::variant<a, b>& v)
{
   // the conditional here might be omitted in the generated
   // code since the branches might be identical
   return v.index() == 0 ? std::get<a>(v) : std::get<b>(v);
}

// use like
base& b = getBase(variant);
b.func(20); 
0 голосов
/ 06 ноября 2018

Что std::variant делает для вас, так это отслеживает текущий (последний назначенный) тип и жалуется, когда вы пытаетесь получить другой тип. Поэтому вместо отслеживания variant_id вы должны использовать variant.index().

Также я полагаю, что ваше первое извлечение base действительно не удастся, если назначенный тип не относится к типу base. Предполагая, что ваши объекты всегда будут иметь тип a или b (и никогда base), вы должны удалить тип base из конструктора варианта варианта.

Я предполагаю, что вы сами не создаете классы base, a и b (и не можете их трогать), и, следовательно, подход с использованием виртуальных методов нежизнеспособен.

...