На мой взгляд, класс должен обеспечивать четко определенную абстракцию, и никакие частные члены не должны изменяться без знания класса. Но когда я проверил «auto_ptr» (или любой другой умный указатель), это правило нарушено. См. Следующий код
class Foo{
public:
Foo(){}
};
int main(int argc, char* argv[])
{
std::auto_ptr<Foo> fooPtr(new Foo);
delete fooPtr.operator ->();
return 0;
}
Перегрузка оператора (->) дает базовый указатель, и его можно изменить без знания «auto_ptr». Я не могу считать это плохим дизайном, так как умные указатели созданы фанатами C++, но мне интересно, почему они это допустили. Есть ли способ написать умный указатель без этой проблемы.
Цени свои мысли.





Нет, полностью запретить такое неправильное использование в C++ невозможно.
Как правило, пользователь любого библиотечного кода никогда не должен вызывать delete для любых обернутых указателей, если это специально не задокументировано. И, на мой взгляд, весь современный код C++ должен быть спроектирован таким образом, чтобы на пользователя классов никогда не возлагалась полная ответственность вручную высвобождать полученные ресурсы (т.е. вместо этого использовать RAII).
Примечание: std::auto_ptr<T> больше не лучший вариант. Его плохое поведение при копировании может привести к серьезным ошибкам кодирования. Часто лучше использовать вместо них std::tr1::scoped_ptr<T> или std::tr1::shared_ptr<T> или их варианты Увеличение.
Более того, в C++ 0x std::unique_ptr<T> функционально заменит std::auto_ptr<T> как более безопасный в использовании класс. Некоторое обсуждение темы и недавнюю реализацию C++ 03 для эмуляции unique_ptr можно найти в здесь.
Спасибо. Я использовал auto_ptr только для объяснения. Я использую boost :: shared_ptr. RAII выглядит интересно
Чтобы обеспечить быстрый, удобный, «похожий на указатель» доступ к базовому объекту, оператор operator->, к сожалению, должен немного «утечь» свою абстракцию. В противном случае интеллектуальные указатели должны были бы вручную заключать в оболочку все члены, которые могут быть открыты. Для этого либо требуется большая работа по «настройке» со стороны тех, кто создает интеллектуальный указатель, либо уровень метапрограммирования, которого просто нет в C++. Кроме того, как указывает Пирста, даже если эта дыра была заткнута, есть еще много других (возможно, нестандартных) способов подорвать механизмы контроля доступа C++.
В этом есть смысл. Спасибо
Is there any way to write a smart pointer without this problem.
Это непросто и, как правило, нет (то есть вы не можете сделать это для каждого общего класса Foo).
Единственный способ, который я могу придумать для этого, - это изменить объявление класса Foo: сделать деструктор Foo закрытым (или определить частный оператор delete как член класса Foo), а также указать в объявлении класса Foo, что std::auto_ptr<Foo> является friend.
Да, я думаю, это единственный способ. Но необходимо изменить Foo и все классы, которые будут использоваться с auto_ptr. Это будет перебор.
Люди, использующие std :: auto_ptr, должны знать, что это такое и как его использовать. Иногда C++ делает невозможным плохое; но иногда это невозможно, и вместо этого он упрощает только хорошие вещи (где «хорошо» - это «помнить об удалении объекта», а плохо - «удалять его более одного раза»).
В C++ разработчику предоставляются все инструменты, чтобы сломать программу. Лучше всего задокументировать поведение, а также правильное использование кода. И следовать лучшим практикам, описанным в других источниках. ;)
Умный указатель должен обладать двумя желательными свойствами:
Очевидно, эти свойства противоречивы и не могут быть реализованы одновременно!shared_ptr<Foo> даже Boost и др. есть get(), значит, у них эта "проблема". На практике первое важнее, поэтому второе должно уйти.
Кстати, я не уверен, почему вы обратились к немного неясному operator->(), когда обычный старый метод get() вызывает ту же проблему:
std::auto_ptr<Foo> fooPtr(new Foo);
delete fooPtr.get();
Re "для перехода к устаревшим библиотечным функциям": нет необходимости вызывать устаревшие функции. Любая функция, которая не заботится о владении не следует, принимает в качестве аргумента умный указатель, но необработанный (или ссылку). Помимо того факта, что право собственности может мешать (многие интеллектуальные указатели не допускают наличия нескольких владельцев), функция не должна навязывать своим клиентам определенный бренд управления владением, если только это не нужно.
@MarcvanLeeuwen: Хороший момент, хотя я бы заменил «не заботится о праве собственности» на «не принимает владение» - каждый интерфейс должен заботиться о своей семантике владения (т.е. быть явным).
@MarcvanLeeuwen: На самом деле ... Можете ли вы представить себе ситуацию, в которой для функции, не связанной с принятием прав собственности, было бы лучше принимать аргумент от T*, а не от T&? Я не могу, и думаю, что второй вариант безопаснее.
Я имел в виду «не заботится» в том смысле, что все, что делает функция, не имеет отношения к владению, а не потому, что она хочет создавать утечки памяти. Но вместо захвата владения это также может быть владение раздавать, сохраняя что-то, что должно принадлежать указателю, переданному по изменяемой ссылке. И из другого комментария, наиболее очевидная ситуация, когда необходим T*, - это когда указатель может быть нулевым. Более гибким аргументом может быть то, что вы знаете, что функция в любом случае должна инициализировать переменную локального указателя; вы также можете передать этот указатель.
Я не думаю, что это показывает, что auto_ptr имеет проблему с инкапсуляцией. При работе с указателями, принадлежащими владельцам, очень важно понимать, кто чем владеет. В случае auto_ptr он владеет указателем, который он содержит [1]; это часть абстракции auto_ptr. Следовательно, удаление этого указателя любым другим способом нарушает контракт, предоставляемый auto_ptr.
Я согласен с тем, что относительно легко неправильно использовать auto_ptr [2], что очень не идеально, но в C++ вы никогда не сможете избежать фундаментальной проблемы «кому принадлежит этот указатель?», Потому что, к лучшему или худшему, C++ не управляет памятью за вас.
[1] Цитата с cplusplus.com: «объекты auto_ptr имеют особенность владения назначенными им указателями»: http://www.cplusplus.com/reference/std/memory/auto_ptr/
[2] Например, вы можете ошибочно полагать, что он имеет семантику значения, и использовать его в качестве параметра векторного шаблона: http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=4&ved=0CEEQFjAD&url=http%3A%2F%2Fwww.gamedev.net%2Ftopic%2F502150-c-why-is- stdvectorstdauto_ptrmytype - плохо% 2F & ei = XU1qT5i9GcnRiAKCiu20BQ & usg = AFQjCNHigbgumbMG3MTmMPla2zo4LhaE1Q & sig2 = WSyJF2eWrq2aB2qw8dF3
Я думаю, что этот вопрос решает несущественную проблему. Умные указатели предназначены для управления владением указателями, и если при этом они делают указатель недоступным, они не выполняют свою задачу.
Также учтите это. Любой тип контейнера дает вам итераторы над ними; если it является таким итератором, то &*it является указателем на элемент в контейнере; если вы скажете delete &*it, то вы мертвы. Но указание адресов его пунктов не является дефектом контейнерного типа.
Я не могу вспомнить много полезных классов, в которых нет ничего подобного. Это удобно, но не глупи с этим.
delete[] &vector[0];