Как передать std :: unique_ptr в функцию

97

Как передать std::unique_ptrв функцию? Допустим, у меня есть следующий класс:

class A
{
public:
    A(int val)
    {
        _val = val;
    }

    int GetVal() { return _val; }
private:
    int _val;
};

Следующее не компилируется:

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);

    return 0;
}

Почему я не могу передать std::unique_ptrв функцию? Конечно, это основная цель конструкции? Или комитет C ++ намеревался, чтобы я вернулся к необработанным указателям в стиле C и передал их следующим образом:

MyFunc(&(*ptr)); 

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

MyFunc(unique_ptr<A>(new A(1234)));
user3690202
источник
7
Нет ничего плохого в «возвращении» к необработанным указателям в стиле C, если они не являются собственными указателями. Возможно, вы предпочтете написать ptr.get (). Хотя, если вам не нужна возможность нулевого значения, предпочтительнее будет ссылка.
Крис Дрю

Ответы:

142

Здесь есть два основных варианта:

Передайте умный указатель по ссылке

void MyFunc(unique_ptr<A> & arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(ptr);
}

Переместите умный указатель в аргумент функции

Обратите внимание, что в этом случае утверждение будет выполнено!

void MyFunc(unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
    unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
    MyFunc(move(ptr));
    assert(ptr == nullptr)
}
Билл Линч
источник
@VermillionAzure: функция, о которой вы говорите, обычно называется не копируемой, а не уникальной.
Bill Lynch
1
Спасибо. В этом случае я хотел создать экземпляр, выполнить с ним некоторые действия, а затем передать право собственности кому-то другому, для чего move () кажется идеальным вариантом.
user3690202
7
Вы должны передавать unique_ptrпо ссылке только в том случае, если функция может или не может перемещаться от нее. И тогда это должна быть ссылка на rvalue. Чтобы наблюдать за объектом, не требуя ничего о семантике его владения, используйте ссылку, например A const&или A&.
Potatoswatter
12
Послушайте выступление Херба Саттерса на CppCon 2014. Он настоятельно не рекомендует передавать ссылки на unique_ptr <>. Суть в том, что вы должны просто использовать умные указатели, такие как unique_ptr <> или shared_ptr <>, когда вы имеете дело с владением. См. Www.youtube.com/watch?v=xnqTKD8uD64 Здесь вы можете найти слайды github.com/CppCon/CppCon2014/tree/master/Presentations/…
schorsch_76
2
@Furihr: Это просто способ показать, какая ценность ptrбудет после переезда.
Билл Линч,
26

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

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

Если время жизни объекта гарантированно существует в течение времени жизни вызова MyFunc, просто передайте необработанный указатель через ptr.get().

Марк Толонен
источник
17

Почему я не могу передать unique_ptrв функцию?

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

12.8 Копирование и перемещение объектов класса

...

7 Если определение класса не объявляет конструктор копии явно, он объявляется неявно. Если определение класса объявляет конструктор перемещения или оператор присваивания перемещения, неявно объявленный конструктор копирования определяется как удаленный;

Вы можете передать unique_ptrфункцию, используя:

void MyFunc(std::unique_ptr<A>& arg)
{
    cout << arg->GetVal() << endl;
}

и используйте его, как будто у вас есть:

или

void MyFunc(std::unique_ptr<A> arg)
{
    cout << arg->GetVal() << endl;
}

и используйте его как:

std::unique_ptr<A> ptr = std::unique_ptr<A>(new A(1234));
MyFunc(std::move(ptr));

Важная заметка

Обратите внимание, что если вы используете второй метод, ptrон не владеет указателем после вызова функции std::move(ptr)возврата.

void MyFunc(std::unique_ptr<A>&& arg)будет иметь такой же эффект, void MyFunc(std::unique_ptr<A>& arg)поскольку оба являются ссылками.

В первом случае ptrвсе еще остается владельцем указателя после вызова MyFunc.

Р Саху
источник
5

Поскольку MyFuncне принимает права собственности, было бы лучше иметь:

void MyFunc(const A* arg)
{
    assert(arg != nullptr); // or throw ?
    cout << arg->GetVal() << endl;
}

или лучше

void MyFunc(const A& arg)
{
    cout << arg.GetVal() << endl;
}

Если вы действительно хотите стать владельцем, вам нужно переместить свой ресурс:

std::unique_ptr<A> ptr = std::make_unique<A>(1234);
MyFunc(std::move(ptr));

или передайте напрямую ссылку на r-значение:

MyFunc(std::make_unique<A>(1234));

std::unique_ptr не имеет копии специально, чтобы гарантировать наличие только одного владельца.

Джарод42
источник
В этом ответе отсутствует то, как выглядит вызов MyFunc для ваших первых двух случаев. Не уверены, используете ли вы ptr.get()или просто передаете ptrв функцию?
taylorstine
Какая подпись предназначена MyFuncдля передачи ссылки на r-значение?
Джеймс Хиршорн
@JamesHirschorn: void MyFunc(A&& arg)берет ссылку на r-значение ...
Jarod42
@ Jarod42 Это то, что я догадался, но с typedef int A[], MyFunc(std::make_unique<A>(N))дает ошибку компилятора: error: неверная инициализация ссылки типа 'int (&&) []' из выражения типа 'std :: _ MakeUniq <int []> :: __ array' { aka 'std :: unique_ptr <int [], std :: default_delete <int []>>'} g++ -std=gnu++11Достаточно недавно?
Джеймс Хиршорн
@ Jarod42 Я подтвердил неудачу для C ++ 14 с помощью ideone: ideone.com/YRRT24
Джеймс Хиршорн
4

Почему я не могу передать unique_ptrв функцию?

Вы можете, но не копированием - потому что std::unique_ptr<>это не копируемо.

Конечно, это основная цель конструкции?

Среди прочего, std::unique_ptr<>он предназначен для однозначной маркировки уникального владения (в отличие от std::shared_ptr<>).

И что самое странное, почему это нормальный способ передать это?

Потому что в этом случае нет конструкции копирования.

Nielk
источник
Спасибо за Ваш ответ. Просто ради интереса, можете ли вы объяснить, почему последний способ передачи не использует конструктор копирования? Я бы подумал, что использование конструктора unique_ptr создаст экземпляр в стеке, который будет скопирован в аргументы MyFunc () с помощью конструктора копирования? Хотя я признаю, что мои воспоминания в этой области немного расплывчаты.
user3690202
2
Поскольку это rvalue, вместо него вызывается конструктор перемещения. Хотя ваш компилятор все равно это оптимизирует.
Nielk
0

Поскольку unique_ptrдля уникального владения, если вы хотите передать его в качестве аргумента, попробуйте

MyFunc(move(ptr));

Но после того, что состояние ptrин mainбудет nullptr.

0x6773
источник
4
"will be undefined" - нет, будет null или unique_ptrбудет бесполезным.
TC
0

Передача в std::unique_ptr<T>качестве значения функции не работает, потому что, как вы, ребята, упомянули, unique_ptrее нельзя скопировать.

Как насчет этого?

std::unique_ptr<T> getSomething()
{
   auto ptr = std::make_unique<T>();
   return ptr;
}

этот код работает

Riste
источник
Если этот код работает, я предполагаю, что он не копирует unique_ptr, а фактически использует семантику перемещения для его перемещения.
user3690202
может быть оптимизация возвращаемого значения (RVO), я думаю
Нуеман Халикин