Существует ли стандартный (или, по крайней мере, безопасный) способ сравнения союзов на равенство в C и / или C ++? Я ожидаю, что побитовое сравнение будет полезно в ряде сценариев несмотря на последнего назначенного члена в каждом союзе; например, конкретный битовый шаблон может быть зарезервирован для обозначения «значение неинициализировано», и было бы полезно иметь возможность проверить, не является ли инициализация неинициализированным, без необходимости указывать «активный» член.
Пример в C ++ (хотя я думаю, что концепция распространяется на C с использованием функций, не являющихся членами):
union MyData
{
public:
// Assume I'm compiling this on a platform where the size of `int`
// exceeds the size of `char*`, or that I'm using some combination
// if `#ifdef`s or similar to ensure that my numeric type is sufficiently
// large, or that I include an extra member that is known to be
// exactly the size of the larger member, just for the sake of
// comparison.
int int_member;
char* ptr_member;
bool isInitialized() const
{
return (*this != INVALID_VAL);
}
bool operator==(MyData const& rhs)
{
return / * ??? */;
}
private:
constexpr MyData INVALID_VAL { /* ??? */ };
}
// ... later, in client code...
MyData d;
bool initialized{d.isInitialized()}; // false
d.ptr_member = new char[32];
bool initialized{d.isInitialized()}; // true
Вот, INVALID_VAL
вероятно, может быть определено путем установки int_member
до максимального отрицательного значения int, поскольку это неравномерное значение, поэтому оно не будет находиться на границе слова и, следовательно, вряд ли когда-либо будет присвоено char*
член (при условии, что задания обычно приходят непосредственно от new
).
Одна возможная реализация operator==
было бы просто:
return int_member == rhs.int_member;
Хотя неизвестно, int_member
является «активным» членом, я ожидаю, что это будет безопасно, потому что я не вижу причин, почему статическое приведение от char*
в int
должен потерпеть неудачу или быть проблемным. Это верно?
Если эта реализация является небезопасно, что-то вроде следующего должно быть возможным (конечно, с использованием приведения в стиле C в C):
return static_cast<void*>(*this) == static_cast<void*>(rhs);
…хотя конечно если MyData
больше, чем размер указателя, вам придется начать возиться с sizeof
чтобы сделать эту работу.
Кто-нибудь делает это? Является ли первая (более простая) реализация безопасной? есть ли смысл не сделать это?
Я думаю, что лучшим подходом было бы заключить ваш союз в класс или структуру с сохранением поля enum, которое было последним членом, к которому обращались, например,
class MyData {
enum {
unintialized, int_member, ptr_member
} last_member = unintialized;
union {
int int_member;
char* ptr_member;
} union_fields;
public:
bool isInitialized() const
{
return last_member != unintialized;
}
};
Инициализация в классе last_member работает, если у вас есть C ++ 11, в противном случае просто инициализируйте его в конструкторе по умолчанию.
Создайте методы доступа для двух полей и установите last_member
соответственно, было бы также хорошо добавить проверки в методы доступа, обеспечивающие доступ только к «активному члену».
Конечно это небезопасно.
Вы не можете предположить, что int
имеет такой же размер, как char *
, например. Также может быть дополнение, которое часто бывает случайным по содержанию.