c ++ 11 — передача аргумента c ++ unique_ptr

Предположим, у меня есть следующий код:

class B { /* */ };

class A {
vector<B*> vb;
public:
void add(B* b) { vb.push_back(b); }
};int main() {

A a;
B* b(new B());
a.add(b);
}

Предположим, что в этом случае все сырые указатели B* может быть обработан через unique_ptr<B>,

Удивительно, но я не смог найти, как конвертировать этот код, используя unique_ptr, После нескольких попыток я придумал следующий код, который компилируется:

class A {
vector<unique_ptr<B>> vb;
public:
void add(unique_ptr<B> b) { vb.push_back(move(b)); }
};int main() {

A a;
unique_ptr<B> b(new B());
a.add(move(b));
}

Поэтому мой простой вопрос: это способ сделать это и, в частности, move(b) единственный способ сделать это? (Я думал о ссылках на rvalue, но не до конца их понимаю.)

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

РЕДАКТИРОВАТЬ Согласно http://thbecker.net/articles/rvalue_references/section_01.html, мой код, кажется, в порядке.

На самом деле, std :: move — это просто синтаксический сахар. С объектом x класса X, move(x) это так же, как:

static_cast <X&&>(x)

Эти 2 функции перемещения необходимы, потому что приведение к rvalue-ссылке:

  1. предотвращает прохождение функции «add» по значению
  2. марки push_back использовать конструктор перемещения по умолчанию B

Видимо, мне не нужен второй std::move в моем main() если я изменю свою функцию добавления для передачи по ссылке (обычное значение lvalue).

Я хотел бы получить подтверждение всего этого, хотя …

11

Решение

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

8

Другие решения

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

Ситуация является вызывающей функцией, которая создает unique_ptr<T> значение (возможно, приведя результат от вызова к new) и хочет передать его какой-то функции, которая получит право собственности на указанный объект (например, сохраняя его в структуре данных, как это происходит здесь, в vector). Чтобы указать, что право собственности было получено вызывающим абонентом, и он готов отказаться от него, передав unique_ptr<T> значение на месте. Насколько я понимаю, есть три разумных способа передачи такого значения.

  1. Передача по значению, как в add(unique_ptr<B> b) в вопросе.
  2. Проходя мимоconst Ссылка на значение, как в add(unique_ptr<B>& b)
  3. Передача по rvalue ссылке, как в add(unique_ptr<B>&& b)

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

Что касается действительного кода, варианты 1 и 3 почти эквивалентны: они заставляют вызывающую сторону записать значение r в качестве аргумента вызова, возможно, путем переноса переменной в вызове std::move (если аргумент уже является значением r, т. е. безымянным, как в приведении от результата newэто не обязательно). В варианте 2, однако, передавая rvalue (возможно, из std::move) является не положено, и функция должна быть вызвана с именем unique_ptr<T> переменная (при передаче newнужно сначала присвоить переменной).

когда std::move действительно используется, переменная, содержащая unique_ptr<T> значение в вызывающей стороне концептуально разыменовывается (преобразуется в rvalue, соответственно преобразуется в ссылку на rvalue), и на этом этапе владение прекращается. В варианте 1. разыменование является действительным, и значение перемещается во временное значение, которое передается вызываемой функции (если функция calles проверяет переменную в вызывающей программе, она обнаружит, что она уже содержит нулевой указатель). Право собственности было передано, и вызывающая сторона не может принять решение не принимать его (бездействие с аргументом приводит к уничтожению указанного значения при выходе из функции; вызов функции release метод в аргументе предотвратит это, но приведет к утечке памяти). Удивительно, но варианты 2. и 3. семантически эквивалентно при вызове функции, хотя для вызывающего абонента требуется другой синтаксис. Если вызываемая функция передаст аргумент другой функции, принимающей значение (например, push_back метод), std::move должны быть вставлены в обоих случаях, что приведет к передаче права собственности на данный момент. Если вызываемая функция забудет что-либо сделать с аргументом, то вызывающая сторона обнаружит, что все еще владеет объектом, если у него есть имя для него (как это обязательно в варианте 2); это несмотря на тот факт, что в случае 3, поскольку прототип функции попросил вызывающую сторону согласиться на освобождение владельца (либо путем вызова std::move или поставка временная). В итоге методы делают

  1. Заставляет вызывающего абонента отказаться от владения и обязательно потребовать его.
  2. Заставить звонящего обладать владеть и быть готовым (предоставляя не const справка) отказаться от нее; Однако это не является явным (нет вызова std::move требуется или даже разрешено), а также не отнимает право собственности. Я бы посчитал этот метод довольно неясным в своем намерении, если только он явно не предназначен для того, чтобы брать на себя ответственность или нет по усмотрению вызываемой функции (можно предположить некоторое использование, но звонящие должны знать)
  3. Заставляет вызывающего абонента явно указывать отказ от владения, как в 1. (но фактическая передача владения задерживается до момента вызова функции).

Вариант 3 достаточно ясен в своем намерении; при условии, что право собственности действительно взято, это для меня лучшее решение. Это немного более эффективно, чем 1, поскольку значения указателя не перемещаются во временные промежутки (вызовы std::move на самом деле просто забрасывают и ничего не стоят); это может быть особенно актуально, если указатель передается через несколько промежуточных функций до его перемещения.

Вот код для эксперимента.

class B
{
unsigned long val;
public:
B(const unsigned long& x) : val(x)
{ std::cout << "storing " << x << std::endl;}
~B() { std::cout << "dropping " << val << std::endl;}
};

typedef std::unique_ptr<B> B_ptr;

class A {
std::vector<B_ptr> vb;
public:
void add(B_ptr&& b)
{ vb.push_back(std::move(b)); } // or even better use emplace_back
};void f() {
A a;
B_ptr b(new B(123)),c;
a.add(std::move(b));
std::cout << "---" <<std::endl;
a.add(B_ptr(new B(4567))); // unnamed argument does not need std::move
}

Как написано, вывод

storing 123
---
storing 4567
dropping 123
dropping 4567

Обратите внимание, что значения уничтожаются в порядке, хранящемся в векторе. Попробуйте изменить прототип метода add (адаптируя другой код, если необходимо, чтобы он компилировался), и действительно ли он передает свой аргумент b, Несколько перестановок строк вывода могут быть получены.

9

Итак, мой простой вопрос: это способ сделать это и, в частности, является ли этот «ход (б)» единственным способом сделать это? (Я думал о ссылках rvalue, но я не совсем понимаю это так …)

И если у вас есть ссылка с полными объяснениями семантики перемещения, unique_ptr … которую я не смог найти, не стесняйтесь.

Бесстыдная вилка, поиск по рубрике «Переезд в члены». Это точно описывает ваш сценарий.

0

Ваш код в main может быть немного упрощен, так как C ++ 14:

a.add( make_unique<B>() );

где вы можете поставить аргументы для Bконструктор внутри внутренних скобок.


Вы также можете рассмотреть функцию-член класса, которая становится владельцем необработанного указателя:

void take(B *ptr) { vb.emplace_back(ptr); }

и соответствующий код в main было бы:

a.take( new B() );

Другой вариант заключается в использовании идеальная пересылка для добавления векторных членов:

template<typename... Args>
void emplace(Args&&... args)
{
vb.emplace_back( std::make_unique<B>(std::forward<Args>(args)...) );
}

и код в основном:

a.emplace();

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

Ссылка на рабочий пример

0
По вопросам рекламы [email protected]