Построение массива векторов C ++ разных типов - PullRequest
0 голосов
/ 26 июня 2018

Есть ли хороший способ построить массив std :: vectors разных типов? Также есть ли хороший способ хранения этих векторов?

Например, пусть у нас есть некоторые структуры Foo, Bar и Baz. Я хочу создать контейнерный класс Cont, содержащий некоторую комбинацию векторов Foo, Bar, Baz. Следующий код достигнет этого, но у меня есть некоторые проблемы с ним.

#include <vector>

// arbitrary structs
struct Foo{ int var1; };
struct Bar{ double var1; double var2; };
struct Baz{ char var1; float var2; };

enum Mask{
    fooMask = (1<<0),
    barMask = (1<<1),
    bazMask = (1<<2)
};

class Cont{
    void** containers;

public:
    Cont(int mask){
        // count number of 1s in mask
        int count = 0;
        int countMask = mask;
        while(countMask){
            countMask &= countMask-1; // unset rightmost 1
            count++;
        }

        containers = new void*[count];

        int index = 0;
        if((mask & fooMask) == fooMask)
            containers[index++] = new std::vector<Foo>;
        if((mask & barMask) == barMask)
            containers[index++] = new std::vector<Bar>;
        if((mask & bazMask) == bazMask)
            containers[index++] = new std::vector<Baz>;
    }
};

int main(){
    // example construction
    Cont c1(fooMask);
    Cont c2(barMask|bazMask);

    return 0;
}

Во-первых, мне не нравится, что я должен хранить массив векторов в пустоте **, но я не мог найти лучший способ.

Во-вторых, если я добавлю новую структуру с именем Qux, мне придется изменить конструктор Cont. Желательно, чтобы для удобства обслуживания я хотел создать массив без необходимости жесткого кодирования типов структуры в класс Cont.

Я пытался использовать шаблоны для решения этой проблемы, но не смог найти решение, которое меня порадовало. Я беспокоюсь о том, чтобы сделать Cont шаблоном, так как думаю, что это приведет к раздутию шаблона для каждой комбинации структур. Также у меня будет несколько Cont объектов, но только по одной из каждой комбинации, которая мне нужна.

1 Ответ

0 голосов
/ 26 июня 2018

Вы можете использовать стирание типа.

struct ContainerBase
{
  virtual ~ContainerBase() = 0;
  // This is where you can add an interface for common functionality.
  // Write the pure virtual functions here and implement/override them in ContainerTyped.
};

inline ContainerBase::~ContainerBase() = default;

template<class T>
struct ContainerTyped : public ContainerBase
{
  std::vector<T> values;
};

class Cont
{
  std::vector<std::unique_ptr<ContainerBase>> containers;

public:
  Cont(int mask) {
    // ...
    if ((mask & fooMask) > 0)
      containers.push_back(std::make_unique<ContainerTyped<Foo>>());
    if ((mask & barMask) > 0)
      containers.push_back(std::make_unique<ContainerTyped<Bar>>());
  }
};

Демо

Это, вероятно, более подходит, чем, например, использовать std::any или стирание другого существующего типа, потому что 1) вы указываете, что только определенные вещи (ваши векторные контейнеры) могут быть сохранены, и 2) вы можете добавить общий интерфейс, как указано, и даже специализировать функции интерфейса по-другому ContainerTyped. Но нам нужно больше узнать о вашем сценарии использования, чтобы подробно описать это преимущество.

Проблема с void* всегда заключается в том, что вам нужно каким-то образом сохранять информацию о том, что вы на самом деле хранили, потому что вы обходите систему строгого типа. Другими словами, как бы вы вернули сохраненную вещь обратно в систему сильных типов? Это как раз та часть, где вышеприведенный подход может проявиться, потому что вы можете добавить virtual print() = 0 в ContainerBase, а затем создать специализированные версии для каждого вида структуры, например,

template<>
void ContainerTyped<Foo>::print()
{
    for (Foo& foo : values) {
        // Print Foo objects as you wish!
    }
}

С точки зрения того, что вам не нужно прикасаться к конструктору Cont при добавлении структуры Qux, вам, очевидно, все равно необходимо кодировать информацию о том, «какой бит маски принадлежит какой-либо структуре», но вы можете извлечь его из Cont конструктор (и даже скрыть его в другом модуле перевода):

// Put this implementation wherever, Cont only has to know the signature.
std::unique_ptr<ContainerBase> makeContainer(int mask, unsigned indexBit)
{
  if ((mask & fooMask) > 0)
    return std::make_unique<ContainerTyped<Foo>>();
  // etc.
  if ((mask & quxMask) > 0)
    return std::make_unique<ContainerTyped<Qux>>();
  return nullptr;
}

// ...

Cont::Cont(int mask)
{
  for (unsigned indexBit = 0; indexBit < 8; ++indexBit) {
    auto container = makeContainer(mask, indexBit);
    if (container)
      containers.emplace_back(std::move(container));
  }
}

Вы можете использовать другие способы кодирования этого enum-> type type, но это выходит за рамки этого вопроса. Ключ в том, что вы можете скрыть свой конкретный тип за ContainerBase и использовать его везде, где вы хотите сослаться на «любой из этих контейнеров».

...