Предположим, у меня есть следующий код:
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-ссылке:
push_back
использовать конструктор перемещения по умолчанию BВидимо, мне не нужен второй std::move
в моем main()
если я изменю свою функцию добавления для передачи по ссылке (обычное значение lvalue).
Я хотел бы получить подтверждение всего этого, хотя …
Да, так и должно быть. Вы явно передаете право собственности от main
в A
, По сути, это то же самое, что и предыдущий код, за исключением того, что оно более явное и значительно более надежное.
Я несколько удивлен тем, что на этот вопрос здесь не очень четко и недвусмысленно дан ответ, и ни в одном месте, на которое я легко наткнулся Хотя я довольно новичок в этом, я думаю, что можно сказать следующее.
Ситуация является вызывающей функцией, которая создает unique_ptr<T>
значение (возможно, приведя результат от вызова к new
) и хочет передать его какой-то функции, которая получит право собственности на указанный объект (например, сохраняя его в структуре данных, как это происходит здесь, в vector
). Чтобы указать, что право собственности было получено вызывающим абонентом, и он готов отказаться от него, передав unique_ptr<T>
значение на месте. Насколько я понимаю, есть три разумных способа передачи такого значения.
add(unique_ptr<B> b)
в вопросе. const
Ссылка на значение, как в add(unique_ptr<B>& b)
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
или поставка временная). В итоге методы делают
const
справка) отказаться от нее; Однако это не является явным (нет вызова std::move
требуется или даже разрешено), а также не отнимает право собственности. Я бы посчитал этот метод довольно неясным в своем намерении, если только он явно не предназначен для того, чтобы брать на себя ответственность или нет по усмотрению вызываемой функции (можно предположить некоторое использование, но звонящие должны знать)Вариант 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
, Несколько перестановок строк вывода могут быть получены.
Итак, мой простой вопрос: это способ сделать это и, в частности, является ли этот «ход (б)» единственным способом сделать это? (Я думал о ссылках rvalue, но я не совсем понимаю это так …)
И если у вас есть ссылка с полными объяснениями семантики перемещения, unique_ptr … которую я не смог найти, не стесняйтесь.
Бесстыдная вилка, поиск по рубрике «Переезд в члены». Это точно описывает ваш сценарий.
Ваш код в 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
внутри скобок.