Подтвердить что ты не робот

Создание shared_ptr из unique_ptr

В куске кода, который я недавно рассмотрел, который скомпилирован с g++-4.6, я столкнулся с странной попыткой создать std::shared_ptr из std::unique_ptr:

std::unique_ptr<Foo> foo...
std::make_shared<Foo>(std::move(foo));

Это кажется мне довольно странным. Это должно быть std::shared_ptr<Foo>(std::move(foo)); afaik, хотя я не совсем знаком с ходами (и я знаю, что std::move - это только приведение, ничего не перемещается).

Проверка с помощью разных компиляторов на этом SSC (NUC *) E

#include <memory>

int main()
{
   std::unique_ptr<int> foo(new int);
   std::make_shared<int>(std::move(foo));
}

Результаты компиляции:

  • g++ - 4.4.7 дает ошибку компиляции
  • g++ - 4.6.4 компиляция без ошибок
  • g++ - 4.7.3 дает внутреннюю ошибку компилятора
  • g++ - 4.8.1 дает ошибку компиляции
  • clang++ - 3.2.1 компилируется без ошибок

Итак, возникает вопрос: какой компилятор прав с точки зрения стандарта? Стандартно ли это требование является недопустимым оператором, действительным оператором или просто это undefined?

Добавление

Мы договорились, что некоторые из этих компиляторов, такие как clang++ и g++ - 4.6.4, разрешают преобразование, пока они не должны. Однако с g++ - 4.7.3 (который создает внутреннюю ошибку компилятора на std::make_shared<Foo>(std::move(foo));), правильно отклоняет int bar(std::move(foo));

Из-за этой огромной разницы в поведении я оставляю вопрос так, как есть, хотя часть его будет отвечать за сокращение до int bar(std::move(foo));.


*) NUC: не универсально компилируемая

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ 2: Этот bug был исправлен в Clang в r191150. GCC отклоняет код с правильным сообщением об ошибке.


ОБНОВЛЕНИЕ: Я отправил отчет об ошибке . Следующий код на моей машине с clang++ 3.4 (trunk 191037)

#include <iostream>
#include <memory>

int main()
{
   std::unique_ptr<int> u_ptr(new int(42));

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   std::cout << "*u_ptr       = " << *u_ptr       << std::endl;

   auto s_ptr = std::make_shared<int>(std::move(u_ptr));

   std::cout << "After move" << std::endl;

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   std::cout << "*u_ptr       = " << *u_ptr       << std::endl;
   std::cout << " s_ptr.get() = " <<  s_ptr.get() << std::endl;
   std::cout << "*s_ptr       = " << *s_ptr       << std::endl;
}

печатает это:

 u_ptr.get() = 0x16fa010
*u_ptr       = 42
After move
 u_ptr.get() = 0x16fa010
*u_ptr       = 42
 s_ptr.get() = 0x16fa048
*s_ptr       = 1

Как вы можете видеть, элемент unique_ptr не был перемещен. Стандарт гарантирует, что он должен быть пустым после его перемещения. shared_ptr указывает на неправильное значение.

Странно, что он компилируется без предупреждения, и valgrind не сообщает о каких-либо проблемах, утечке, отсутствии кучи. Weird.

Правильное поведение показано, если я создаю s_ptr с shared_ptr ctor, беря rvalue ref вместо unique_ptr вместо make_shared:

#include <iostream>
#include <memory>

int main()
{
   std::unique_ptr<int> u_ptr(new int(42));

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   std::cout << "*u_ptr       = " << *u_ptr       << std::endl;

   std::shared_ptr<int> s_ptr{std::move(u_ptr)};

   std::cout << "After move" << std::endl;

   std::cout << " u_ptr.get() = " <<  u_ptr.get() << std::endl;
   //std::cout << "*u_ptr       = " << *u_ptr       << std::endl; // <-- would give a segfault
   std::cout << " s_ptr.get() = " <<  s_ptr.get() << std::endl;
   std::cout << "*s_ptr       = " << *s_ptr       << std::endl;
}

Он печатает:

 u_ptr.get() = 0x5a06040
*u_ptr       = 42
After move
 u_ptr.get() = 0
 s_ptr.get() = 0x5a06040
*s_ptr       = 42

Как вы видите, u_ptr имеет значение null после перемещения, как требуется стандартным, и s_ptr указывает на правильное значение. Это правильное поведение.


(оригинальный ответ.)

Как прост указал: "Если Foo не имеет конструктора, который принимает std:: unique_ptr, он не должен компилироваться".

Чтобы немного расширить его: make_shared пересылает свои аргументы в конструктор T. Если T не имеет никакого ctor, который мог бы принять, что unique_ptr<T>&& это ошибка компиляции.

Однако этот код можно легко исправить и получить то, что вы хотите (онлайн-демонстрация):

#include <memory>
using namespace std;

class widget { };

int main() {

    unique_ptr<widget> uptr{new widget};

    shared_ptr<widget> sptr(std::move(uptr));
}

Дело в том, что make_shared - это неправильная вещь для использования в этой ситуации. shared_ptr имеет ctor, который принимает unique_ptr<Y,Deleter>&&, см. (13) в ctors shared_ptr.

Ответ 2

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

int *u = new int;
int *s = new int(std::move(u));

Это означает, что он динамически создает int и инициализирует его ссылкой rvalue на std::unique_ptr<int>. Для int s это просто не должно компилироваться.

Для общего класса Foo он зависит от класса. Если он имеет конструктор, принимающий значение std::unique_ptr<Foo> по значению, const ref или rvalue ref, он будет работать (но, возможно, не делать то, что автор намеревался). В других случаях он не должен компилироваться.

Ответ 3

Здесь приведенный пример, который clang неправильно компилирует:

struct ptr
{
  int* p;

  explicit operator bool() const { return p != nullptr; }
};

int main()
{
  ptr u{};
  int* p = new int(u);
}

Clang использует явный оператор преобразования bool для инициализации int (и компилятор Intel тоже делает это.)

Clang 3.4 не позволяет:

int i = int(u);

но это позволяет:

int* p = new int(u);

Я думаю, что оба должны быть отвергнуты. (Clang 3.3 и ICC допускают оба.)

Я добавил этот пример в отчет .