Мне вызвали проблему путем нарушения строгого правила искажения указателя. У меня есть тип T
это прибывает из шаблона и некоторого целочисленного типа Int
из того же размера (как с sizeof
). Мой код по существу делает следующее:
T x = some_other_t;
if (*reinterpret_cast <Int*> (&x) == 0)
...
Поскольку T
некоторый arbitary (кроме ограничения размера) тип, который мог иметь конструктора, я не могу сделать объединение T
и Int
. (Это позволяется только в C++ 0x только и еще даже не поддерживается GCC).
Есть ли какой-либо способ, которым я мог переписать вышеупомянутый псевдокод, чтобы сохранить функциональность и постараться не нарушать строгое правило искажения? Обратите внимание, что это - шаблон, я не могу управлять T
или значение some_other_t
; присвоение и последующее сравнение действительно происходят в шаблонном коде.
(Для записи вышеупомянутый код начал повреждаться на GCC 4.5 если T
содержит любые битовые поля.)
static inline int is_T_0(const T *ob)
{
int p;
memcpy(&p, ob, sizeof(int));
return p == 0;
}
void myfunc(void)
{
T x = some_other_t;
if (is_T_0(&x))
...
На моей системе GCC оптимизирует как is_T_0()
, так и memcpy()
, в результате чего в myfunc()
остается всего несколько инструкций ассемблера.
Вы слышали о boost :: optional
?
Должен признать, я не понимаю, в чем заключается настоящая проблема ... но boost :: optional позволяет хранить по значению и все же знать, есть ли или не была инициализирована фактическая память. Я также допускаю строительство и разрушение на месте, так что я думаю, это может быть хорошо.
РЕДАКТИРОВАТЬ :
Думаю, я наконец понял проблему: вы хотите иметь возможность выделять много объектов в различных точках памяти, и вы хотите знать, используется ли память в эта точка действительно удерживает объект или нет.
К сожалению, ваше решение содержит огромную проблему: оно неверно. Если когда-либо T
каким-то образом может быть представлен битовой комбинацией null
, то вы подумаете, что это унифицированная память.
Вам придется прибегнуть к добавлению хотя бы одного бита информации. На самом деле это немного, ведь это всего 3% прироста (33 бита на 4 байта).
Вы можете, например, использовать некоторый mimick boost :: optional
, но в виде массива (чтобы избежать потери заполнения).
template <class T, size_t N>
class OptionalArray
{
public:
private:
typedef unsigned char byte;
byte mIndex[N/8+1];
byte mData[sizeof(T)*N]; // note: alignment not considered
};
Тогда это так просто:
template <class T, size_t N>
bool OptionalArray<T,N>::null(size_t const i) const
{
return mIndex[i/8] & (1 << (i%8));
}
template <class T, size_t N>
T& OptionalArray<T,N>::operator[](size_t const i)
{
assert(!this->null(i));
return *reinterpret_cast<T*>(mData[sizeof(T)*i]);
}
примечание : Для простоты я не рассматривал вопрос согласования. Если вы не знаете о предмете, прочтите об этом, прежде чем возиться с памятью :)
Как насчет этого:
Int zero = 0;
T x = some_other_t;
if (std::memcmp(&x, &zero, sizeof(zero)) == 0)
Это может быть не так эффективно, но должно избавить от предупреждения.
ДОБАВЛЕНИЕ #1:
Поскольку T
ограничено тем же размером, что и Int
, создайте себе фиктивное значение типа T
с побитовым нулем и сравнивайте непосредственно с ним (вместо приведения и сравнения с Int(0)
).
Если ваша программа однопоточная, вы можете получить что-то вроде этого:
template <typename T>
class Container
{
public:
void foo(T val)
{
if (zero_ == val)
{
// Do something
}
}
private:
struct Zero
{
Zero() {memset(&val, 0, sizeof(val));}
bool operator==(const T& rhs) const {return val == rhs;}
T val;
};
static Zero zero_;
};
Если она многопоточная, вы захотите избежать использования статического члена zeroo_
, и пусть каждый экземпляр контейнера содержит свой собственный член zeroo_
:
template <typename T>
class MTContainer
{
public:
MTContainer() {memset(zero_, 0, sizeof(zero_));}
void foo(T val)
{
if (val == zero_)
{
// Do something
}
}
private:
T zero_;
};
ДОБАВЛЕНИЕ #2:
Позвольте мне сформулировать вышеприведенное дополнение другим, более простым способом:
// zero is a member variable and is inialized in the container's constructor
T zero;
std::memset(&zero, 0, sizeof(zero));
T x = some_other_t;
if (x == zero)
Это похоже на взлом, но, видимо, я нашел решение: использовать volatile
для приведения Int
. По сути, сейчас я делаю следующее:
T x = some_other_t;
if (*reinterpret_cast <volatile Int*> (&x) == 0)
...
Проблема с битовым полем T
теперь исчезла. Тем не менее, я не очень доволен этим, поскольку volatile
не очень четко определен в C ++ AFAIK ...