Глюк в STL (vector)
От: lis82  
Дата: 17.12.08 14:04
Оценка: -1
При работе программа выдает Access Violation.
Текст программы:
#include <vector>

class foo
{
private:
    char * String;
    void SetStr(char * str);
public:
    foo();
    foo(const foo & e);
    foo(char * str);
    ~foo();
};

int main(int argc, char* argv[])
{
    std::vector<foo> Err;
    Err.push_back(foo("first"));
    Err.push_back(foo("second"));
    Err.erase(Err.begin()); //Здесь почему-то удаляется элемент "second"
    Err.erase(Err.begin());//Ошибка возникает здесь 
    return 0;
}
foo::foo()
{
    String=NULL;
}
foo::foo(const foo &e)
{
    String=NULL;
    SetStr(e.String);
}
foo::foo(char *str)
{
    String=NULL;
    SetStr(str);
}
foo::~foo()
{
    if(String)
        delete String;
}
void foo::SetStr(char * str)
{
    if(String!=NULL)
    {
        delete String;
        String=NULL;
    }
    size_t Length=strlen(str)+1;
    if(Length>0)
    {
        String=new char[Length];
        strcpy(String,str);
    }
}

Компилировал и в Builder2006 и в Studio2005 — результат одинаковый.
Я думаю, что дело в библиотеке STL:
Вот что я нарыл:
<vector>
...
    iterator erase(iterator _Where)
        {    // erase element at where
        _STDEXT unchecked_copy(_VEC_ITER_BASE(_Where) + 1, _Mylast,
            _VEC_ITER_BASE(_Where));
        _Destroy(_Mylast - 1, _Mylast);
        --_Mylast;
        return (_Where);
        }
...

Т.е. сначала идет копирование элементов в новое место (затирая содержимое _Where), а затем удаляется последний в списке.
Но ведь по логике сначала нужно удалить содержимое _Where, и только затем его можно затирать.

Может кто знает это баг или фича?
Re: Глюк в STL (vector)
От: Smal Россия  
Дата: 17.12.08 14:15
Оценка: 2 (2) +1
Здравствуйте, lis82, Вы писали:

оверквотинг вырезан — Кодт

L>Может кто знает это баг или фича?


Это требования к элементам контейнера — они должны быть не тольк copy-constructable,
но и assignable (т.е. поддерживать традиционную семантику присваивания).
В твоём случае это означает, что нужно определить operator=.

И ещё я заметил у тебя ошибку:
если ты вызываешь new[], то удалять надо при помощи delete [].
С уважением, Александр
Re: Глюк в STL (vector)
От: Аноним  
Дата: 17.12.08 14:50
Оценка:
Здравствуйте, lis82, Вы писали:

Оверквотинг вырезан — Кодт

Да вот ОПЕРАТОР Присвоения В данном ллучаа ОБЯЗАТЕЛЕН..
Сам подумай оператор сгенерированный автаматически просто приравняет один указатель к другому и при удалении ты Это наблюдаеш .. Сначало удаляется второй обект .. по тому что пи копировании первому Элементу присвоитмся новое значение а память из первого просто потеряется.. а при у даленни второго будет удалятся память повторно ...
Re[2]: Глюк в STL (vector)
От: Аноним  
Дата: 18.12.08 15:39
Оценка: -3
Здравствуйте, Smal, Вы писали:

S>И ещё я заметил у тебя ошибку:

S>если ты вызываешь new[], то удалять надо при помощи delete [].

для простых типов да и для структур и классов в которых в деструкторе ничего не происходит, можно delete [] не писать.
Re: Глюк в STL (vector)
От: Аноним  
Дата: 18.12.08 15:48
Оценка:
Здравствуйте, lis82, Вы писали:

L>

L>[/c#]
L>Компилировал и в Builder2006 и в Studio2005 — результат одинаковый.
L>Я думаю, что дело в библиотеке STL:
L>Вот что я нарыл:
L><vector>
L>
L>...
L>    iterator erase(iterator _Where)
L>        {    // erase element at where
L>        _STDEXT unchecked_copy(_VEC_ITER_BASE(_Where) + 1, _Mylast,
L>            _VEC_ITER_BASE(_Where));
L>        _Destroy(_Mylast - 1, _Mylast);
L>        --_Mylast;
L>        return (_Where);
L>        }
L>...
L>

L> Т.е. сначала идет копирование элементов в новое место (затирая содержимое _Where), а затем удаляется последний в списке.
L>Но ведь по логике сначала нужно удалить содержимое _Where, и только затем его можно затирать.

L>Может кто знает это баг или фича?



это все от 2005 и начинается
понапридумывали разных левых проверок, в 2003 и ниже все должно быть ок(никаких operator= перегружать не надо)
и вроде, я не тестил то при

#define _CRT_SECURE_NO_DEPRECATE
#define _HAS_ITERATOR_DEBUGGING 0
#define _SECURE_SCL 0

в stdafx.h
все должно заработать.
+
а зачем ты используешь char* ?
написал бы
std::string String вместо char* String;
и не надо никаких конструкторов копирования и operator=.
Re[3]: Так глюк будет ещё и в С++ :)
От: Erop Россия  
Дата: 18.12.08 16:05
Оценка:
Здравствуйте, Аноним, Вы писали:

S>>если ты вызываешь new[], то удалять надо при помощи delete [].

А>для простых типов да и для структур и классов в которых в деструкторе ничего не происходит, можно delete [] не писать.

Это особенность реализации некоторых компиляторов. И только!!!
Закладываться на это нехорошо!!!

Одним из способов реализации new[] delete[] является следующий. Если у объектов, создаваемых по new[] есть нетривиальный деструктор, то аллокируется память с запасом, в начало памяти кладут счётчик объектов (чтобы потом вызвать деструкторы у нужного числа объектов), а объекты располагают за этим счётчиком. Ну и из выражения new T[count] возвращают указатель не на начало блока, а на первый из объектов.

Соответсвенно, когда ты делаешь delete[], то в зависимости от того, есть ли у типа тривиальный dtor ищут счётчик перед переданным в delete[] адресом или нет. И освобождают блок, соотетветственно, либо начинающийся с адреса первого объекта, либо начинающийся раньше. А выражение delete p сразу считает, что блок начинается с того адреса, откуда начинается объект.

Конечно, если разрушается массив объектов, про деструктор которого такая реализация решила, что они имеют тривиальный деструктор, то всё будет хорошо. Но если она вдруг от чего-то подумает иначе, то ты попробуешь удалить адрес, находящийся в сериедине блока -- это капец куче

Можешь попробовать на "дефолтном компиляторе" что-то вроде следующего:
struct SCrashBase {
    virtual ~SCrash() {}
};
struct SCrash : SCrashBase {};

void foo( int count )
{
    delete new SCrash[count];
}
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Глюк в STL (vector)
От: -MyXa- Россия  
Дата: 18.12.08 16:12
Оценка: +1
Здравствуйте, Аноним, Вы писали:

А>для простых типов да и для структур и классов в которых в деструкторе ничего не происходит, можно delete [] не писать.


Это, конечно, не так.

из 5.3.5.2

...In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array newexpression. If not, the behavior is undefined.


Но меня интересует другое — кто и зачем занимается подобным мифотворчеством и как такие мифы проникают в умы?
Если не поможет, будем действовать током... 600 Вольт (C)
Re[4]: Так глюк будет ещё и в С++ :)
От: Аноним  
Дата: 18.12.08 16:43
Оценка:
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, Аноним, Вы писали:



E>Одним из способов реализации new[] delete[] является следующий. Если у объектов, создаваемых по new[] есть нетривиальный деструктор, то аллокируется память с запасом, в начало памяти кладут счётчик объектов (чтобы потом вызвать деструкторы у нужного числа объектов), а объекты располагают за этим счётчиком. Ну и из выражения new T[count] возвращают указатель не на начало блока, а на первый из объектов.


Как узнать на с++ есть ли у класса свой деструктор?
Re[4]: Глюк в STL (vector)
От: Sergey Россия  
Дата: 18.12.08 16:56
Оценка: +1 :))
"-MyXa-" <46238@users.rsdn.ru> wrote in message news:3220067@news.rsdn.ru...

> А>для простых типов да и для структур и классов в которых в деструкторе ничего не происходит, можно delete [] не писать.

>
> Это, конечно, не так.
>
>

> из 5.3.5.2
>
> ...In the second alternative (delete array), the value of the operand of delete shall be the pointer value which resulted from a previous array newexpression. If not, the behavior is undefined.

>
> Но меня интересует другое — кто и зачем занимается подобным мифотворчеством и как такие мифы проникают в умы?

Это всего лишь наблюдаемое поведение для VC-6 — VC-8. Оно не вызывает деструкторы если не поставить скобки и падает если поставить лишние. Ну а VC-9 еще круче — оно на непоставленные скобки говорит человечьим языком- "мужик, ты там скобки забыл — я их за тебя поставил на всякий случай"
Posted via RSDN NNTP Server 2.1 beta
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Re[3]: Глюк в STL (vector)
От: Аноним  
Дата: 18.12.08 17:12
Оценка:
Здравствуйте, Аноним, Вы писали:

А>для простых типов да и для структур и классов в которых в деструкторе ничего не происходит, можно delete [] не писать.


Якобы "существующая" взаимозаменяемость delete и delete[] обсуждалась неоднократно. Например, здесь
Автор:
Дата: 22.03.06
Re[5]: Так глюк будет ещё и в С++ :)
От: Erop Россия  
Дата: 18.12.08 17:12
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Как узнать на с++ есть ли у класса свой деструктор?

Переносимо -- никак...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Глюк в STL (vector)
От: Кодт Россия  
Дата: 18.12.08 18:04
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>это все от 2005 и начинается

А>понапридумывали разных левых проверок, в 2003 и ниже все должно быть ок(никаких operator= перегружать не надо)

Фигасе! Это начинается со Стандарта. Сказано, что элемент должен быть CopyConstructible и Assignable.
То, что разные реализации по-разному выполняют erase — это их личное дело.
Есть по крайней мере три способа:
template<class T>
void drop_first(T* arr, size_t count)
{
    assert(count > 0);

    // первый способ: серия убийств-копирований
    for(size_t i=0; i!=count; ++i)
    {
        arr[i].~T();
        new(&arr[i]) T(arr[i+1]);
    }
    arr[count-1].~T();
    
    // второй способ: серия присваиваний
    for(size_t i=0; i!=count-1; ++i)
        arr[i] = arr[i+1];
    arr[count-1].~T();
    
    // третий способ: серия обменов
    for(size_t i=0; i!=count-1; ++i)
        swap(arr[i], arr[i+1]);
    arr[count-1].~T();
}

Очевидно, что первый способ — не лучше (вероятно, всё-таки хуже) второго. А второй и третий конкурируют, в зависимости от того, что быстрее: копирующее присваивание или обмен (который по умолчанию реализован через копирование, два присваивания и убийство)
namespace std
{
    template<class T>
    void swap(T& a, T& b)
    {
        T c(a);
        a=b;
        b=c;
    }
}

// рукодельный
void swap(foo& a, foo& b)
{
    swap(a.String, b.String); // всяко быстрее, чем реконструирование
}

APROPOS: было бы неплохо, чтобы компилятор сам мог рожать неявный почленный swap наряду с копированием и присваиванием.
Кто знаток C++0x, есть такая фича в новом Стандарте? Или мечты, мечты?...


А>написал бы

А>std::string String вместо char* String;
А>и не надо никаких конструкторов копирования и operator=.

А вот это — хорошее предложение.
Если, конечно, топикстартер не решает упражнения из учебника: тогда ты просто отнимаешь удочку!
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[5]: Так глюк будет ещё и в С++ :)
От: Alexander G Украина  
Дата: 18.12.08 19:21
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Как узнать на с++ есть ли у класса свой деструктор?


Приблизительный ответ здесь
Русский военный корабль идёт ко дну!
Re[3]: Глюк в STL (vector)
От: Юрий Жмеренецкий ICQ 380412032
Дата: 18.12.08 23:22
Оценка:
Здравствуйте, Кодт, Вы писали:
...
К>Сказано, что элемент должен быть CopyConstructible и Assignable.
Но ведь для Assignable не требуется наличие явно определенного 'operator ='.

К>То, что разные реализации по-разному выполняют erase — это их личное дело.

К>Есть по крайней мере три способа:

...
 // второй способ: серия присваиваний
    for(size_t i=0; i!=count-1; ++i)
        arr[i] = arr[i+1];
    arr[count-1].~T();
...

Только этот может быть использован, т.к:

23.2.4.3/4 (vector::erase)
The destructor of T is called the number of times equal to the number of the elements erased,
but the assignment operator of T is called the number of times equal to the number of elements in the
vector after the erased elements.


В оригинальном коде (если исправить delete -> delete[]):
 std::vector<foo> Err;
 Err.push_back(foo("first"));  
 Err.push_back(foo("second")); 
 Err.erase(Err.begin()); // здесь memory leak. 
 //..

В соответствии с 23.2.4.3/4 после первого вызова erase в векторе остается элемент, который не удовлетворяет Assignable требованиям т.к. он содержит инвалидный указатель (после delete, 5.3.5/4) и для последующего копирования объекта будет использован implicitly-declared copy assignment operator, который использует почленное присваивание (это приведет к UB).

Но до этого в указанном коде дела не доходит, ошибка возникает еще раньше — перемещение объектов в результате второго вызова erase приведет к вызову деструктора у объекта, который содержит инвалидный указатель:
 if(String)  // <-- 3.7.3.2/4 The effect of using an invalid pointer value is undefined. 
   delete[] String; // То же самое
Re[4]: Глюк в STL (vector)
От: Кодт Россия  
Дата: 19.12.08 00:15
Оценка:
Здравствуйте, Юрий Жмеренецкий, Вы писали:

К>>Сказано, что элемент должен быть CopyConstructible и Assignable.

ЮЖ>Но ведь для Assignable не требуется наличие явно определенного 'operator ='.

Можно и неявный — главное, чтобы он был корректным!
А в исходной ситуации присваивание ведёт к двойному владению строкой и, впоследствии, к неопределённому поведению.

ЮЖ>
ЮЖ>    for(size_t i=0; i!=count-1; ++i)
ЮЖ>        arr[i] = arr[i+1];
ЮЖ>    arr[count-1].~T();
ЮЖ>

ЮЖ>Только этот может быть использован, т.к:
ЮЖ>[q]23.2.4.3/4 (vector::erase)

Аха, буду знать.
Хотя жаль, конечно: на обменах в некоторых случаях могло бы быть эффективнее.
Перекуём баги на фичи!
Re[5]: Глюк в STL (vector)
От: Юрий Жмеренецкий ICQ 380412032
Дата: 19.12.08 02:57
Оценка: 62 (3)
Здравствуйте, Кодт, Вы писали:
...
ЮЖ>>Только этот может быть использован, т.к:
ЮЖ>>23.2.4.3/4 (vector::erase)

К>Аха, буду знать.

К>Хотя жаль, конечно: на обменах в некоторых случаях могло бы быть эффективнее.

Скоро будет счастье =)
Assignable переименовали в CopyAssignable, добавили Swappable, Destructible, а так же:

MoveConstructible
T t = rv, ... There is no requirement on the value of rv after the
construction.

MoveAssignable
t = rv, ... There is no requirement on the value of rv after the
construction.


Требования к элементам контейнеров сильно ослабили:

Objects stored in these components shall be MoveConstructible and
MoveAssignable.


+От объектов требуется быть CopyConstructible и/или CopyAssignable в отдельных случаях вроде использования конструктора копирования и 'operator=' контейнера и совсем небольшого набора операций.

+ CopyAssignable от объектов ассоциативных контейнеров не требуется (требуется только для ключей).

+ Возвращаясь к std::vector<T>::erase:

The destructor of T is called the number of times equal to the number of the elements erased, but the
move assignment operator of T is called the number of times equal to the number of elements in the vector after
the erased elements.

+...
это еще на окончательная версия, но в library defect reports прослеживаются некоторые тенденции:

DR#276:
...
Rationale:
list, set, multiset, map, multimap are able to store non-Assignables. However, there is some concern about list<T>: although in general there's no reason for T to be Assignable, some implementations of the member functions operator= and assign do rely on that requirement. The LWG does not want to forbid such implementations.
...
In principle we could also relax the "Assignable" requirement for individual vector member functions, such as push_back. However, the LWG did not see great value in such selective relaxation. Doing so would remove implementors' freedom to implement vector::push_back in terms of vector::insert.



Swappable requirements в первоначальном варианте выражались в виде:

T is Swappable if T satisfies the CopyConstructible requirements (Table 33) and the CopyAssignable requirements...


Позже поменяли:

DR#672
...
Proposed resolution:
Change 20.1.1:

T is Swappable if T satisfies the MoveConstructible requirements (Table 34 ) and the MoveAssignable requirements.
...
[ Kona (2007): We like the change to the Swappable requirements to use move semantics...]

 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.