Практически безопасно принимать sizeof (std:: unordered_map <std::string, T>) одинаково для всех T?

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

В упрощенных выражениях мне нужна упрощенная версия того, что происходит:

struct Map;

struct Node {
    // some interface...
private:
    // this cannot be done because Map is an incomplete type
    char buffer[sizeof(Map)];
    // plus other stuff...
    void* dummy;
};

struct Map {
    // some interface...
private:
    // this is Map only member
    std::unordered_map<std::string, Node> map_;
};

Ситуация на самом деле более сложная, чем предыдущая, поскольку Node на самом деле будет вариантом типа (аналогичным boost::variant), который использует размещение new для явного построения одного из нескольких типов объектов в предварительно распределенном (и с правильным выравниванием, которое я игнорирую в этом упрощении) буфер: поэтому буфер не точно sizeof(Map), а скорее некоторая вычисленная константа, зависящая от sizeof(Map).

Проблема, очевидно, заключается в том, что sizeof(Map) недоступен, когда Map объявляется только вперед. Кроме того, если я сначала меняю порядок деклараций на отправку объявления Node, тогда компиляция Map терпит неудачу, поскольку std::unordered_map<std::string, Node> не может быть создан, когда Node является неполным типом, по крайней мере, с моим GCC 4.8.2 на Ubuntu. (Я знаю, что это зависит от версии libstdС++ больше, чем от версии GCC, но я не знаю, как это найти...)

В качестве альтернативы, я рассматриваю следующее обходное решение:

struct Node {
    // some interface...
private:
    // doing this instead of depending on sizeof(Map)
    char buffer[sizeof(std::unordered_map<std::string, void*>)];
    // other stuff...
    void* dummy;
};

struct Map {
    // some interface...
private:
    // this is Map only member
    std::unordered_map<std::string, Node> map_;
};

// and asserting this after the fact to make sure buffer is large enough
static_assert (sizeof(Map) <= sizeof(std::unordered_map<std::string, void*>),
    "Map is unexpectedly too large");

В основном это полагается на предположение, что std::unordered_map<std::string, T> является одним и тем же размером для всех T, что, похоже, справедливо для моего тестирования с использованием GCC.

Мой вопрос, таким образом, имеет три аспекта:

  • Есть ли что-нибудь в стандарте С++, требующем, чтобы это предположение сохранялось? (Я предполагаю, что нет, но если есть, я был бы приятно удивлен...)

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

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

Ответ 1

Просто продолжайте и предполагайте. Тогда static_assert при построении вы правы.

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

Ответ 2

1) Нет

2) Контейнеры STL не могут быть созданы с неполным типом. Однако, по-видимому, некоторые компиляторы этого позволяют. Не допущение этого не было тривиальным решением, и во многих случаях ваше предположение действительно будет справедливым. Эта статья может заинтересовать вас. Учитывая тот факт, что в соответствии со стандартом эта проблема не разрешима без добавления слоя косвенности, и вы не хотите этого делать. Я просто должен дать тебе голову, ты действительно не делаешь вещи в соответствии со стандартом.

Сказав это, я думаю, что ваше решение является лучшим с использованием контейнеров stl. И static assert действительно будет предупреждать, когда размер действительно превышает ожидаемый размер.

3) Да, добавив еще один слой косвенности, мое решение будет следующим:

Проблема заключается в том, что размер объекта зависит от размера его массивов. Скажем, у вас есть объект A и объект B:

struct A
{
   char sizeof[B]
}

struct B
{
    char sizeof[A]
}

Объект A будет расти, чтобы разместить символы для размера B. Но тогда, в свою очередь, объект B должен будет расти. Наверное, вы можете видеть, где это происходит. Я знаю, что это ваша точная проблема, но я думаю, что основные принципы очень похожи.

В этом конкретном случае я решил бы это, изменив

char buffer[sizeof(Map)];

Строка - это просто указатель:

char* buffer

И динамически выделять память после инициализации. Sow ваш файл cpp будет выглядеть примерно так:

//node.cpp
//untested code
node::node()
{
    buffer = malloc(sizeof(map));
}

node::~node()
{
    free buffer;
}

Ответ 3

1) Нет

2) Я не уверен

3) Вы также можете использовать шаблон дизайна Factory. Ваш factory будет возвращать объект на основе варианта Map (EDIT: я имею в виду, что вы будете использовать экземпляр варианта карты в качестве параметра, а реализация метода factory будет использовать эту информацию для создания возвращаемого объекта соответственно) и он может предварительно распределить буфер для правильного размера.

Ответ 4

1) Вероятно, не

2) Поскольку он выглядит полностью зависящим от реализации, этот большой риск, поскольку это может буквально ломаться при любом обновлении компилятора.

3) Вы можете использовать boost::unordered_map, который будет принимать неполные типы и, таким образом, решит вашу проблему.