C memset, кажется, не пишет каждому члену - PullRequest
6 голосов
/ 13 апреля 2009

Я написал небольшой класс координат для обработки как int, так и float координат.

template <class T>
class vector2
{
public:
    vector2() { memset(this, 0, sizeof(this)); }
    T x;
    T y;
};

Затем в main () я делаю:

vector2<int> v;

Но согласно моему отладчику MSVC, только значение x установлено на 0, значение y остается неизменным. Я никогда раньше не использовал sizeof () в шаблонном классе, может ли это быть причиной проблемы?

Ответы [ 7 ]

25 голосов
/ 13 апреля 2009

Нет, не используйте memset - он обнуляет размер указателя (4 байта на моей машине Intel x86), начиная с местоположения, указанного this. Это плохая привычка: вы также обнуляете виртуальные указатели и указатели на виртуальные базы при использовании memset со сложным классом. Вместо этого сделайте:

template <class T>
class vector2
{
public:
    // use initializer lists
    vector2() : x(0), y(0) {}
    T x;
    T y;
};
16 голосов
/ 13 апреля 2009

Как говорят другие, memset() - неправильный способ сделать это. Однако есть некоторые тонкости, почему нет.

Во-первых, ваша попытка использовать memset() очищает только sizeof(void *) байт. В нашем примере это, по-видимому, совпадение байтов, занятых элементом x.

Простым решением было бы написать memset(this, 0, sizeof(*this)), который в этом случае установил бы x и y.

Однако, если в вашем классе vector2 есть какие-либо виртуальные методы и обычный механизм используется для их представления вашим компилятором, тогда этот memset уничтожит vtable и сломает экземпляр, установив указатель vtable в NULL. Что плохо.

Другая проблема состоит в том, что если для типа T требуется какое-либо более сложное действие конструктора, чем просто установка его битов в 0, то конструкторы для элементов не вызываются, но их эффект разрушается перезаписью содержание членов с memset().

Единственное правильное действие - написать конструктор по умолчанию как

vector2(): x(0), y(0), {}

и просто забыть о попытке использовать memset() для этого вообще.

Edit: D.Shawley указал в комментарии, что конструкторы по умолчанию для x и y действительно были вызваны до memset() в исходном коде, как представлено. Хотя это технически верно, вызов memset() перезаписывает участников, что в лучшем случае действительно, очень плохая форма, а в худшем случае вызывает демонов Неопределенного поведения.

Как написано, класс vector2 - это POD, если тип T также является обычными старыми данными, как если бы T были int или float.

Однако все, что нужно для того, чтобы T был своего рода классом значения bignum, вызывать проблемы, которые действительно трудно диагностировать. Если вам повезет, они начнут проявляться на ранних этапах через нарушения прав доступа из-за разыменования указателей NULL, созданных memset(). Но Lady Luck - непостоянная любовница, и более вероятным результатом будет утечка памяти, и приложение становится «шатким». Или, скорее, «потряснее».

ОП спросил в комментарии к другому ответу: "... Есть ли способ заставить memset работать?"

Ответ прост: «Нет».

Выбрав язык C ++ и полностью воспользовавшись шаблонами, вы должны заплатить за эти преимущества, правильно используя язык. Просто не правильно обходить конструктор (в общем случае). Хотя существуют обстоятельства, при которых законно, безопасно и разумно вызывать memset() в программе на C ++, это просто не один из них.

5 голосов
/ 13 апреля 2009

Проблема в том, что это тип Pointer, который составляет 4 байта (в 32-битных системах), а int - 4 байта (в 32-битных системах). Попробуйте:

sizeof(*this)

Изменить: Хотя я согласен с другими, что списки инициализатора в конструкторе, вероятно, является правильным решением здесь.

4 голосов
/ 13 апреля 2009

прямо верно. Однако вместо того, чтобы конструировать x и y с 0, явный вызов конструктора по умолчанию установит для внутренних типов 0 и позволит использовать шаблон для структур и классов с конструктором по умолчанию.

template <class T>
class vector2
{
public:
    // use initializer lists
    vector2() : x(), y() {}
    T x;
    T y;
};
4 голосов
/ 13 апреля 2009

Не используйте memset. Это ужасно сломается на не POD-типах (и не обязательно будет легко отлаживаться), и в этом случае, вероятно, будет намного медленнее, чем простая инициализация обоих членов нулем (два назначения против вызова функции).

Более того, вы обычно не хотите обнулять всех членов класса. Вы хотите обнулить те, для которых ноль является значимым значением по умолчанию. И вы должны привыкнуть инициализировать своих членов к значимой ценности в любом случае. Общее обнуление всего и притворство, что проблемы не существует, просто гарантирует много головных болей позже. Если вы добавляете члена в класс, решите, должен ли этот член быть инициализирован и как.

Если и когда вам нужна функциональность, подобная memset, по крайней мере, используйте std :: fill, который совместим с не POD-типами.

Если вы программируете на C ++, используйте инструменты, доступные в C ++. В противном случае назовите это C.

3 голосов
/ 13 апреля 2009

Не пытайтесь быть умнее компилятора. Используйте списки инициализаторов, как это предусмотрено языком. Компилятор знает, как эффективно инициализировать базовые типы.

Если вы попытаетесь использовать свой memset для взлома класса с виртуальными функциями, вы, скорее всего, перезапишите vtable, который закончится катастрофой. Не пользуйтесь таким хаком, это кошмар обслуживания.

1 голос
/ 18 апреля 2009

Это может работать вместо:


char buffer[sizeof(vector2)];
memset(buffer, 0, sizeof(buffer));
vector2 *v2 = new (buffer) vector2();

.. или замена / переопределение vector2 :: new, чтобы сделать что-то подобное. Мне все еще кажется странным.

Определенно идти с

<code>
vector2(): x(0), y(0), {}
...