Это хороший вариант использования std::auto_ptr<>?

Пожалуйста, предположим, у меня есть функция, которая принимает указатель в качестве параметра. Эта функция может генерировать исключение, так как она использует std::vector<>::push_back() управлять жизненным циклом этого указателя. Если я объявлю это так:

void manage(T *ptr);

и назовите это так:

manage(new T());

если он генерирует исключение, толкая указатель в std::vector<>У меня фактически есть утечка памяти, не так ли?

Будет ли объявление функции следующим образом:

void manage(std::auto_ptr<T> ptr);

решить мою проблему?

Я бы ожидал, что он сначала выделит std::auto_ptr в стеке (что, я полагаю, никогда не вызовет исключение) и позволит ему получить владение указателем. Безопасный.

Затем внутри функции я бы вставил необработанный указатель в std::vector<>, что также безопасно: если это не удастся, указатель не будет добавлен, но умный указатель все еще будет владеть указателем, поэтому он будет уничтожен. Если нажатие выполнено успешно, я бы удалил владение умным указателем над этим указателем и возвратился: это не может вызвать исключение, поэтому всегда будет хорошо.

Верны ли мои теории?

-- Редактировать --

Нет, я думаю, что не могу этого сделать. Для этого потребуется неконстантная ссылка на rvalue (чтобы отобрать владение у умного указателя). Я бы написал

std::auto_ptr<T> ptr(new T());
manage(ptr);

чтобы это работало, что-то, что довольно неудобно в моем случае. Я пишу это так, чтобы я мог реализовать RAII, не загрязняя много кода. Тогда это не поможет. Это было бы поймать 22.

- Правка 2 -

Взяв за основу то, что сказал здесь Джейсон Орендорфф, для быстрого ознакомления читателей, окончательное решение выглядит следующим образом:

void manage(T *ptr)
{
    std::auto_ptr<T> autoPtr(ptr);
    vector.push_back(ptr);
    autoPtr.release();
}

Это решает проблему бесполезной неконстантной ссылки на rvalue.

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

- Правка 3 -

Хорошо, здесь было много дискуссий, и есть ключевые моменты, которые я должен был прояснить раньше. В общем, когда я пишу в stackru, я пытаюсь объяснить причину моих вопросов, и, в общем, это совершенно бесполезно. Так что в этот раз я подумал, что должен перейти прямо к делу. Оказывается, это не очень хорошо работает XD

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

Я действительно новый программист на C++, и мой фокус - разработка игр. Когда в игровой движок выдается исключение, это конец выполнения. Система освободит всю память для моего процесса, поэтому не имеет значения, если один или два указателя просочатся туда-сюда. Сейчас, когда я занимаюсь разработкой серверного приложения, мне трудно работать с исключениями, потому что исключение не может привести к сбою сервера; он должен "сбить запрос".

То есть, "клиент, к сожалению, разработчики не предусмотрели это условие, поэтому вам придется попробовать его позже (вплоть до этого, это в основном то же самое, что и с игровым движком, ничего не ремонтируется, только он изолирован в контексте только запроса, а не всего процесса. Но не паникуйте, поскольку все осталось в допустимом состоянии (однако, здесь есть одно из отличий. Процесс не завершен, поэтому операционная система не может освободить ресурсы для вас, кроме того, вы должны уделять внимание отмене операций до сих пор, чтобы вы не блокировали, например, учетную запись пользователя или даже полный сервис, предоставляемый сервером).".

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

Большое спасибо за ваши ответы, мне действительно нравится stackru. Удивительно, как быстро на мои вопросы отвечают и насколько просвещающими являются ваши ответы. Благодарю.

4 ответа

Решение

Я думаю, что было проведено достаточно дискуссий, чтобы оправдать еще один ответ.

Во-первых, чтобы ответить на реальный вопрос, да, абсолютно уместно (и даже необходимо!) Передавать аргумент с помощью умного указателя, когда происходит передача права собственности. Передача с помощью умного указателя является обычной идиомой для достижения этой цели.

void manage(std::auto_ptr<T> t) {
    ...
}
...

// The reader of this code clearly sees ownership transfer.
std::auto_ptr<T> t(new T);
manage(t);

Теперь есть очень веская причина, по которой все умные указатели имеют явные конструкторы. Рассмотрим следующую функцию (мысленно заменить std::auto_ptr с boost::shared_ptr если это щекочет ваше воображение)

void func(std::auto_ptr<Widget> w, const Gizmo& g) {
    ...
}

Если std::auto_ptr имел неявный конструктор, вдруг этот код скомпилируется:

func(new Widget(), gizmo);

Что с этим не так? Взяв почти напрямую из "Эффективного C++", пункт 17:

func(new Widget(), a_function_that_throws());

Поскольку в C++ порядок оценки аргументов не определен, аргументы могут быть оценены в следующем порядке: new Widget(), a_function_that_throws(), std::auto_ptr конструктор. Если функция выбрасывает, у вас есть утечка.

Поэтому все ресурсы, которые будут освобождены, должны быть переданы в конструкцию в классах RAII перед передачей в функцию. Это означает, что все умные указатели должны быть построены перед передачей их в качестве аргумента функции. Создание умных указателей, которые можно копировать с помощью константной ссылки или неявно-конструируемых, будет стимулировать небезопасный код. Явная конструкция обеспечивает безопасность кода.

Теперь, почему бы вам не сделать что-то подобное?

void manage(T *ptr)
{
    std::auto_ptr<T> autoPtr(ptr);
    vector.push_back(ptr);
    autoPtr.release();
}

Как уже упоминалось, интерфейсные идиомы говорят мне, что я могу передать принадлежащий мне указатель и удалить его. Итак, ничто не мешает мне сделать это:

T item;
manage(&t);

// or
manage(&t_class_member);

Что, конечно, катастрофично. Но вы скажете: "Конечно, я знаю, что означает интерфейс, я бы никогда не использовал его таким образом". Однако вы можете захотеть добавить дополнительный аргумент в функцию позже. Или кто-то (не вы, или вы через 3 года) придут с этим кодом, они могут не увидеть его так же, как вы.

  1. Этот гипотетический "кто-то еще" может видеть только заголовок без комментариев и (справедливо) предполагать отсутствие передачи права собственности.
  2. Они могут видеть, как эта функция используется в каком-то другом коде, и копировать использование, не глядя на заголовок.
  3. Они могут использовать автозаполнение кода для вызова функции, а не для чтения комментария или функции и предположения об отсутствии передачи права собственности.
  4. Они могут написать функцию, которая обернет вас manage функция, и все же кто-то еще будет использовать функцию обертки и пропустит документацию исходной функции.
  5. Они могут попытаться "расширить" ваш код, чтобы весь старый код компилировался (и автоматически становился небезопасным):

    void manage(T *t, const std::string tag = some_function_that_throws());
    

Как видите, явное построение умного указателя значительно усложняет написание небезопасного кода в вышеупомянутых случаях.

Поэтому я не рекомендовал бы идти вразрез с десятилетиями опыта C++, чтобы сделать заметно "приятнее" и "веселее" API.

Мой 2с.

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

Если вы используете что-то вроде boost:: shared_ptr (я верю, что что-то подобное есть и в библиотеках TR1 - в качестве примера смотрите реализацию MS), вы можете забыть о необходимости очистки в случае, если все идет по плану.

Чтобы сделать это, вам нужно, чтобы ваш вектор принял boost::shared_ptr < T > экземпляр, тогда вы просто позволите очистить исходный экземпляр, и он оставит экземпляр в векторе живым, если все пойдет хорошо. В случае, если все пойдет не так boost::shared_ptr экземпляры будут уничтожены, и вы все равно не получите утечки.

С умными указателями речь идет о выборе того, который соответствует задаче, и в этом случае, похоже, цель - совместное владение (или простая передача владения), поэтому на большинстве платформ есть более подходящие кандидаты, чем std::auto_ptr.

В общем, используя std::auto_ptr в качестве аргумента типа функции недвусмысленно сообщает вызывающей стороне, что функция станет владельцем объекта и будет нести ответственность за его удаление. Если ваша функция соответствует этому описанию, то обязательно используйте auto_ptr там, независимо от любой другой причины.

Да, это нормально. (См. Правку ниже.) (Jkp может видеть что-то, что я пропустил, но я не думаю, что вам "все равно придется очистить в случае возникновения исключения", потому что, как вы говорите, в этом случае auto_ptr удалит объект для вас.)

Но я думаю, что было бы лучше скрыть shenanigans auto_ptr от вызывающей стороны:

void manage(T *t) {
    std::auto_ptr<T> p(t);  // to delete t in case push_back throws
    vec.push_back(t);
    p.release();
}

РЕДАКТИРОВАТЬ: я изначально написал "Да, это хорошо", ссылаясь на оригинал manage(auto_ptr<T>) планирую, но я попробовал это и обнаружил, что это не работает. Конструктор auto_ptr<T>::auto_ptr(T *) является explicit, Компилятор не позволит вам написать manage(new T) потому что он не может неявно преобразовать этот указатель в auto_ptr, manage(T *) в любом случае, более дружественный интерфейс!

Другие вопросы по тегам