Практически безопасно принимать 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
, который будет принимать неполные типы и, таким образом, решит вашу проблему.