Очищается ли std :: unique_ptr автоматически после области видимости, если он указан как ссылка? - PullRequest
2 голосов
/ 27 февраля 2020

Можно вернуться с указателем на функцию, и это полезно по многим причинам, но предлагается ли брать ссылку из этого возвращаемого значения?

#include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> give_a_number(){
    return std::make_unique<int>(6);
}

int main(){
   int& ret = *give_a_number();
   return ret;
}

Согласно дезинфицирующему средству, утечки не происходит:

user@comp: Scrapbook$ g++ main.cpp -fsanitize=address -g
user@comp: Scrapbook$ 

Так что ссылка как-то очищена, но я не понимаю, почему. Как происходит очистка, и даже как unque_ptr может отслеживать данные. Должно ли это быть воспринято как безопасное поведение? Это общепринятое использование в сообществе?

Ответы [ 2 ]

10 голосов
/ 27 февраля 2020

Здесь нет утечки. В общем случае объект, принадлежащий std::unique_ptr, никогда не просочился, если .release() не вызывается для владельца std::unique_ptr.

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

give_a_number() возвращает по значению std::unique_ptr, владеющий объектом int. Поэтому std::unique_ptr материализуется как временный объект в операторе

int& ret = *give_a_number();

Вы не перемещаете этот временный объект ни в один постоянный экземпляр std::unique_ptr. Поэтому, когда временный объект уничтожается в конце оператора, int, которым он владеет, также уничтожается. Теперь ваша ссылка свисает.

Затем вы используете висячую ссылку в return ret;, вызывая неопределенное поведение.

Вы можете сохранить ссылку на принадлежащий объект, но если вы это сделаете, вы должен гарантировать, что экземпляр std::unique_ptr, владеющий ссылкой, устареет ссылка int, чтобы избежать проблем с жизненным циклом. Например, все в порядке:

int main(){
   return *give_a_number(); // `std::unique_ptr` temporary outlives return value initialization
}
int main(){
   auto ptr = give_a_number(); // `ptr` lives until `main` returns
   int& ret = *ptr;
   return ret;
}

Обычно безопаснее просто сохранить смарт-указатель в автоматической переменной c, как указано выше, и получить объект путем разыменования с помощью *ptr, где это необходимо. , std::unique_ptr и принадлежащий ему объект затем живут до конца блока, если он не перемещен из.

Но совершенно правильно, например, передать *ptr в функцию по ссылке. Поскольку ptr переживает такой вызов функции, проблем не будет.

Это также работает, если вы передаете *give_a_number() непосредственно в качестве аргумента функции, потому что временный std::unique_ptr также переживет такой вызов. Но в этом случае std::unique_ptr и принадлежащий ему объект будут жить только до конца оператора (полного выражения), а не до конца блока.

3 голосов
/ 27 февраля 2020

unique_ptr живет только в выражении с give_a_number . Указатель на int уничтожается до return .

Можно увидеть, как он себя ведет, используя не POD A с ведением журнала деструктором и вызовами конструктора.

struct A
{
  A() { std::cout << "A created\n"; }
  ~A() { std::cout << "A destroyed\n"; }
};

unique_ptr<A> give_a_number(){
  return std::unique_ptr<A>(new A());
}

int main(){
  std::cout << "start\n";
  A& ret = *give_a_number();
  std::cout << "reference to removed object here\n";
  return 0;
}

Вывод это:

start
A created
A destroyed
reference to removed object here

Я не знаю, почему sanitizer не печатает предупреждение в вашем случае, потому что мое делает при вызове вашего кода. Я использую g ++ 5.3.1

==21820==ERROR: AddressSanitizer: heap-use-after-free on address 0x60200000eff0 at pc 0x000000400bd5 bp 0x7ffee5f3cac0 sp 0x7ffee5f3cab8
READ of size 4 at 0x60200000eff0 thread T0
...