Здравствуйте!
Я, конечно, еще только начинающий специалист в программировании на С++, но меня, однако, удивляет тот факт, что в новом 11 стандарте внесены рад изменений, вызывающих оживленные споры (как и все новое), а некоторые явные неудобства оставлены без изменений. Возьмем конкретный пример с выделением и освобождением памяти:
class A
{
public:
int *p;
A::A()
{
p = new int;
}
A::~A()
{
if (p) delete p;
}
void set()
{
if (p) delete p;
p = new int;
}
void unset()
{
if (p) delete p;
//p = NULL; // without this the program is received SIGSEGV
}
};int main()
{
A a;
a.set();
a.unset();
return 0;
}Мы создаем объект класса и выделяем память полю класса методом set().
Затем, в некоторый момент времени мы освобождаем память методом unset().
При вызове деструктора если явно не обнулить указатель, он будет удален дважды и программа упадет.
Почему же в новом стандарте среди прочих изменений не добавили обнуление указателя оператором delete ?
Если кто-то считает, что автоматическое обнуление указателя здесь неуместно, пожалуйста, приведите пример, где нам может понадобиться значение указателя после его освобождения - я с такой ситуацией не сталкивался.
> Почему же в новом стандарте среди прочих изменений не добавили обнуление указателя
> оператором delete ?Не шибко силён в С++, но полагаю, что это из-за самой природы оператора delete. На скорую руку в Википедии нашёл такое его определение:
void operator delete(void*) throw();То есть оператору передаётся значение указателя, а не его адрес. А это значит, что оператор не знает, где находится указатель, следовательно и обнулить его не может. И действительно, этот указатель может вообще нигде не "находиться", а быть результатом вычисления какого-либо выражения или вызова функции. Что в таком случае будете обнулять?
PS. Не берусь судить, хорошо это или плохо.
Смотрите в сторону smart pointer, попробуйте применить например unique_ptr для Вашего примераnew и delete достаточно низкоуровневые операции, и одни делают ровно то для чего созданны.
>[оверквотинг удален]
>
>[оверквотинг удален]
>
> Мы создаем объект класса и выделяем память полю класса методом set().
> Затем, в некоторый момент времени мы освобождаем память методом unset().
> При вызове деструктора если явно не обнулить указатель, он будет удален дважды
> и программа упадет.
> Почему же в новом стандарте среди прочих изменений не добавили обнуление указателя
> оператором delete ?
> Если кто-то считает, что автоматическое обнуление указателя здесь неуместно, пожалуйста,
> приведите пример, где нам может понадобиться значение указателя после его освобождения
> - я с такой ситуацией не сталкивался.Почитайте про value type и reference type, многое объяснит.
В Си есть похожая проблема: функция free() не может обнулить свой аргумент, потому что он передаётся по значению.
> Почитайте про value type и reference type, многое объяснит.
> В Си есть похожая проблема: функция free() не может обнулить свой аргумент,
> потому что он передаётся по значению.Думаю, это единственное логическое объяснение такому поведению
>> Почитайте про value type и reference type, многое объяснит.
>> В Си есть похожая проблема: функция free() не может обнулить свой аргумент,
>> потому что он передаётся по значению.
> Думаю, это единственное логическое объяснение такому поведениюА также, как я понимаю, комитет по стандарту C++ старается не требовать в стандарте языка абсолютно ничего что может привести к утрате эффективности и без чего можно обойтись. Требование обнулять указатель добавляет дополнительные команды в генерируемый код.
Если вы хотите удобства программирования, как уже тут говорили, есть shared pointers в библиотеке. Но иногда нужно выжать из процессора максимум, где и одна дополнительная машинная операция на обнуление мешает. Вот дизайн C++ и учитывает подобные вещи.
>[оверквотинг удален]
>>> потому что он передаётся по значению.
>> Думаю, это единственное логическое объяснение такому поведению
> А также, как я понимаю, комитет по стандарту C++ старается не требовать
> в стандарте языка абсолютно ничего что может привести к утрате эффективности
> и без чего можно обойтись. Требование обнулять указатель добавляет дополнительные команды
> в генерируемый код.
> Если вы хотите удобства программирования, как уже тут говорили, есть shared pointers
> в библиотеке. Но иногда нужно выжать из процессора максимум, где
> и одна дополнительная машинная операция на обнуление мешает. Вот дизайн C++
> и учитывает подобные вещи.Я думаю тут дело всё таки не в дополнительной инструкции: объект передаётся по значению, поэтому технически внутри вызываемой функции нельзя изменить значение аргумента в вызвавшей функции.
Даже если бы существовал такой способ, то это не имело бы значения, поскольку Страуструп задумывал С++ как расширение к Си, поэтому подмножество Си нельзя было менять. Поскольку в Си всё передаётся по значению, то и в сишной части С++ пришлось заложить такое поведение.
А вот почему в Си всё передаётся по значению - это вопрос не к комитету и не к Страуструпу, а к Ритчи.
> Я думаю тут дело всё таки не в дополнительной инструкции: объект передаётся
> по значению, поэтому технически внутри вызываемой функции нельзя изменить значение аргумента
> в вызвавшей функции.Теоретически конструкция
delete ptr;
не есть вызовом функции, и компилятор это обрабатывает не как вызов функции (да, в эту обработку входит вставка кода вызова operator delete() если он определен); поэтому я не вижу технических причин почему бы не потребовать от компилятора вставку кода для обнуления указателя после завершения вызова operator delete().
> поэтому я не вижу технических причин почему бы не
> потребовать от компилятора вставку кода для обнуления указателя после завершения вызова
> operator delete().Повторю свой вопрос: что будете обнулять, если параметр, переданный оператору delete, - это результат вычисления выражения или вызова функции?
Правильный ответ: ничего, поскольку в общем случае компилятор не знает, где лежит этот указатель.
Можно, правда, ввести хитрый delete, так чтобы компилятор ловил простые случаи типа "delete p", когда ясно, что именно нужно обнулить. Но это не спасёт, потому что указателей на один и тот же объект теоретически может быть сколько угодно. Один обнулите, а остальные останутся указателями "в никуда".
Короче, проблема несколько глубже, чем кажется на первый взгляд, и изменением поведения одного лишь оператора delete её не решить.
PS. Она полностью решается столь нелюбимыми многими сборщиками мусора ;-) Да, создавая при этом некоторые новые проблемы, но это уже отдельный разговор.
> Можно, правда, ввести хитрый delete, так чтобы компилятор ловил простые случаи типа
> "delete p", когда ясно, что именно нужно обнулить. Но это не
> спасёт, потому что указателей на один и тот же объект теоретически
> может быть сколько угодно. Один обнулите, а остальные останутся указателями "в
> никуда".Я у себя (когда приспичит чего-нить попрограммировать) использую функцию (примерно):
template <typename T>
inline void delete0(T*& ptr) {
delete ptr;
ptr = 0; // Да, nullptr тоже хорошо раз уж в теме c++11 :).
}
Понятное дело, это не спасет от множества указателей, но выдаст ошибку компиляции при попытке скормить ей не переменную, а что-то еще.
> Можно, правда, ввести хитрый delete, так чтобы компилятор ловил простые случаи типа
> "delete p", когда ясно, что именно нужно обнулить. Но это не
> спасёт, потому что указателей на один и тот же объект теоретически
> может быть сколько угодно. Один обнулите, а остальные останутся указателями "в
> никуда".Так именно такой хитрый delete я и имел в виду. И я абсолютно согласен с тем что это не решит всех проблем.
> PS. Она полностью решается столь нелюбимыми многими сборщиками мусора ;-) Да, создавая
> при этом некоторые новые проблемы, но это уже отдельный разговор.Или smart pointers - например, boost::shared_ptr/boost::weak_ptr. Недавно я решил использовать boost в одном довольно значительном проекте, включая boost'овские smart poiners - стиль программирования изменился и стал напоминать стиль используемий при работе с Java - намного проще работать с динамической памятью. В общем мне понравилось. После этого был проект где по разным не зависящим от нас причинам мы не могли использовать boost - так мне оказалось проще напасать свой маленький темплейт-класс shared pointer'а, чем в ручную прослеживать кто сказал new и кто потом должен сказать delete.