2009-01-31 2 views
2

Вчера я прочитал код своего сослуживца и наткнулся на это:Удаление всех членов класса

class a_class 
{ 
public: 
    a_class() {...} 
    int some_method(int some_param) {...} 

    int value_1; 
    int value_2; 
    float value_3; 
    std::vector<some_other_class*> even_more_values; 
    /* and so on */ 
} 

a_class a_instances[10]; 

void some_function() 
{ 
    do_stuff(); 
    do_more_stuff(); 

    memset(a_instances, 0, 10 * sizeof(a_class)); // <===== WTF? 
} 

Это правовой (ВТФ линии, а не государственные атрибуты)? Для меня это действительно пахнет, действительно плохо ... Код сработал отлично при компиляции с VC8, но он генерирует «неожиданное исключение» при компиляции с VC9 при вызове a_instances[0].event_more_values.push_back(whatever), но при доступе к любому другому члену. Какие-нибудь идеи?

РЕДАКТИРОВАТЬ: Изменено значение от memset(&a_instances... до memset(a_instances.... Спасибо, что указали на Эдуарда.
EDIT2: Удален тип возврата ctor. Спасибо, лампочка.

Заключение: Спасибо, ребята, вы подтвердили мое подозрение.

ответ

9

Это широко применяемый метод инициализации для структур C.
В C++ это не работает, потому что вы не можете ничего принять о внутренней структуре vector. Обнуление его, скорее всего, оставит его в незаконном состоянии, поэтому ваша программа выйдет из строя.

4

Я не уверен, но я думаю, что memset удалит внутренние данные вектора.

4

Когда вы обнуляете a_instances, вы также обнуляете std_vector внутри. Что, вероятно, выделяет буфер при построении. Теперь, когда вы пытаетесь push_back, он видит указатель на буфер NULL (или какой-либо другой внутренний член), поэтому он генерирует исключение.

Это не законно, если вы спросите. Это потому, что вы не можете перегружать запись через указатели, поскольку вы можете перегружать операции присваивания.

+0

Хорошо, вы меня там. Конечно, в исходном коде это не было и не было, но a_instances. Изменен код соответственно. Благодарю. – EricSchaefer

+0

Хорошо, я меняю свой ответ. –

3

Вы не должны делать memset для объектов C++, потому что он не вызывает соответствующий конструктор или деструктор.

В частности, в этом случае деструктор even_more_values ​​ члена все a_instances «ы элементов не вызываются.

Фактически, по крайней мере, с указанными вами членами (до/* и т. Д. * /) Вам не нужно вызывать memset или создавать специальные функции деструктора или clear(). Все эти члены автоматически удаляются деструктором по умолчанию.

+0

Ну, вы * можете *, но ... – dmckee

+0

исправлено на «не должно» :-) –

+0

:: пожимает плечами :: Я понял. Просто ощущение противоположности в ленивое субботнее утро. – dmckee

3

Вы должны реализовать метод «ясный» в своем классе

void clear() 
    { 
    value1=0; 
    value2=0; 
    value_3=0f; 
    even_more_values.clear(); 
    } 
+0

Самое смешное: класс имеет метод clear(). – EricSchaefer

+0

ясный не нужен. even_more_values ​​- это вектор, а не указатель –

+0

@ Я, а, да, вы правы ... Я собираюсь отредактировать этот код – Pierre

5

Он использует memset для типа класса, отличного от POD. Это недопустимо, потому что C++ разрешает это только для простейших случаев: если класс не имеет объявленного конструктора, деструктора, никаких виртуальных функций и еще нескольких ограничений. Массив объектов этого не изменит этого факта.

Если он удаляет вектор, он в порядке с использованием memset на нем. Но одно замечание. Даже если это не C++, он все равно может быть применим для его компилятора - потому что, если стандарт говорит, что что-то имеет неопределенное поведение, реализации могут делать все, что они хотят, включая благословение такого поведения и высказывание того, что происходит. В его случае, вероятно, что вы применяете memset к нему, и он молча очищает любые элементы вектора. Возможные указатели в нем, указывающие на выделенную память, теперь будут содержать только нуль, не зная об этом.

Вы можете рекомендовать его, чтобы очистить его, используя что-то вроде этого:

... 
for(size_t i=0; i < 10; i++) 
    objects[i].clear(); 

И писать ясно, используя нечто вроде:

void clear() { 
    a_object o; 
    o.swap(*this); 
} 

Перекачка бы просто поменять вектор орто с одной из * это и очистить другие переменные. Обмен дешевле. Ему, конечно же, нужно написать функцию свопинга, которая меняет вектор (even_more_values.swap(that.even_more_values)) и другие переменные.

+0

Не обращайте внимания на тип возврата ctor. Я написал выше код из верхней части головы. – EricSchaefer

+0

спасибо, я изменил свой ответ в ответ на ваше редактирование :) –

+0

Как я писал в комментарии к Пьеру, у класса был уже правильный метод clear(), но он не использовал его. После того, как я сказал ему, что у меня болит голова с помощью memset, он изменил код на заявление, как и вы предлагаете. – EricSchaefer

3

Худшая часть этого заключается в том, что если у вектора было что-то в нем, эта память теперь теряется, потому что конструктор не был вызван.

НИКОГДА не перезаписывайте объект C++. КОГДА-ЛИБО. Если это был производный объект (и я не знаю специфику std :: vector), этот код также переписывает виртуальную таблицу объекта, делая ее crashy, а также поврежденной.

Тот, кто написал это, не понимает, какие объекты есть и что вам нужно, чтобы объяснить, что они собой представляют и как они работают, чтобы они не совершали такую ​​ошибку в будущем.

+0

Это никогда не должно переписывать объект, отличный от POD. См. Ответ на вопрос о том, что такое POD. – KeithB

+0

Я сомневаюсь, что потеря нескольких слов предварительно выделенного буфера в векторах действительно представляет большую проблему по сравнению с нарушениями доступа, которые будут происходить. Удивительно, что это когда-либо работало. – Eclipse

+0

Дело в том, что если std :: vector не имеет vtable (и, следовательно, не имеет косвенности), И это происходит, чтобы использовать NULL для обозначения того, что внутренние указатели не заполнены, тогда вы можете уйти с топанием его ко всем нулям. Вы все равно потеряете память, но вы, возможно, не потерпите крах. –

2

То, что у вас здесь, может не произойти сбой, но, вероятно, оно не будет делать то, что вы хотите! Обнуление вектора не вызовет деструктор для каждого экземпляра a_class. Он также перезапишет внутренние данные для a_class.even_more_values (так что если ваш push_back() после , вы, скорее всего, получите нарушение доступа).

Я хотел бы сделать две вещи по-разному:

  1. Использование STD :: вектор для хранения как в a_class и в some_function().
  2. Написать деструктор a_class, очищающий правильно

Если вы это сделаете, хранилище будет управляться для вас компилятором автоматически.

Например:

class a_class 
{ 
public: 
    a_class() {...} 
    ~a_class() { /* make sure that even_more_values gets cleaned up properly */ } 

    int some_method(int some_param) {...} 

    int value_1; 
    int value_2; 
    float value_3; 
    std::vector<some_other_class*> even_more_values; 
    /* and so on */ 
} 

void some_function() 
{ 
    std::vector<a_class> a_instances(10); 

    // Pass a_instances into these functions by reference rather than by using 
    // a global. This is re-entrant and more likely to be thread-safe. 
    do_stuff(a_instances); 
    do_more_stuff(a_instances); 

    // a_instances will be cleaned up automatically here. This also allows you some 
    // weak exception safety. 
} 

Помните, что если even_more_values содержит указателей на другие объекты, вам нужно будет удалить эти объекты в деструкторе a_class. Если возможно, even_more_values должен содержать сами объекты, а не указатели на эти объекты (таким образом вам может не потребоваться написать деструктор для a_class, который может быть достаточным для компилятора).

Смежные вопросы