std::список< стандартный::unique_ptr<T> >:передавая его по кругу
-
13-09-2020 - |
Вопрос
Скажем, у меня есть std::list
от class T
'с:
std::list<T> l;
При передаче его в функции я бы использовал ссылку:
someFunction( std::list<T> &l )
Каков наилучший способ передачи (элементов) std::list
от unique_ptr
с?
std::list< std::unique_ptr<T> > l;
Подобный этому:
someFunction( std::unique_ptr<T> ptr )
Или это:
someFunction( T* ptr )
Или это:
someFunction( T &ref )
И что, как бы я назвал это, используя std::list
'с back()
функция, например?ИМХО, все это "своего рода" эквиваленты, но я уверен, что здесь я что-то упускаю.
Спасибо
Решение
В порядке от лучшего к худшему:
- Некоторая функция(const T&);
- Некоторая функция(T&);
- Некоторая функция(const std::unique_ptr<T>&);
- Некоторая функция(std::unique_ptr<T>&);
Первый вариант является лучшим, потому что он не изменяет объект it и будет работать с объектом независимо от того, как вы его выделили (например, вы могли бы переключиться на shared_ptr без проблем).
Номер два также будет работать независимо от того, какой интеллектуальный указатель вы используете;однако предполагается, что вы можете изменить объект, и всякий раз, когда вы можете сделать что-то постоянным, вы должны это сделать.
Числа 3 и 4 позволяют изменять объект, на который указывают;однако #3 не позволяет изменять интеллектуальный указатель, в то время как номер 4 позволяет.Оба имеют тот недостаток, что они вынуждают использовать unique_ptr, тогда как два приведенных выше варианта будут работать независимо от класса интеллектуального указателя.
Передача unique_ptr по значению, как у вас есть в некоторых других примерах, не является вариантом;предполагается, что unique_ptr должен быть уникальным.Если вы копируете его, рассмотрите возможность использования shared_ptr.
Для первых двух, если бы вы вызвали его по результату back(), это выглядело бы как:
someFunction(*(lst.back())); // dereference lst.back() before passing it in.
Для последних двух, если бы вы вызвали его при повторном вызове back(), это выглядело бы как:
someFunction(lst.back()); // pass the smart pointer, not the object to
// which the smart pointer currently points.
Другие советы
Делать нет проходить unique_ptr
по значению, во-первых, он не будет компилироваться без std::move
и если ты делать использовать std::move
это приведет к удалению значения, которое вы сохранили в своем list
и вы больше не сможете получить к нему доступ.
Это потому что unique_ptr
не поддается копированию, у него нет конструктора копирования типа unique_ptr::unique_ptr(const unique_ptr<T>& other)
вместо этого у него есть только конструктор перемещения (unique_ptr::unique_ptr(unique_ptr<T>&& source)
).
unique_ptr, а также классы / экземпляры, содержащие unique_ptr, могут использоваться в std::list (и других контейнерах) при условии, что они имеют конструктор перемещения class_name(class_name &&)
определен (который, конечно же, есть у unique_ptr).
Когда вы передаете эти элементы, вы всегда перемещаете (а не копируете) их, поэтому вы всегда используете std::move() для значений lvalues, как в
my_list.push_back(std::move(my_element));
это делает видимым, что вы передаете (= перемещаете) элемент в список, и что my_element является "пустым" (например, пустым unique_ptr) после этой операции.
Пример:
typedef std::unique_ptr<uint8_t[]> data_ptr;
class data_holder
{
private:
int something_about_data;
data_ptr data;
public:
data_holder(data_ptr && _data)
: data(std::move(_data))
{}
// hey compiler, please generate a default move constructor for me
// despite of present destructor
data_holder(data_holder &&) = default;
~data_holder()
{
// ...
}
bool is_empty() const { return ! bool(data); }
}
// ...
{
// ...
data_holder the_element(data_ptr(new uint8_t[DATA_SIZE]));
// move the_element into the_list
the_list.push_back(std::move(the_element));
if (the_element.is_empty())
std::cerr << "data_holder 'the_element' is now empty!" << std::endl;
// ...
}