Страницы

Поиск по вопросам

вторник, 31 марта 2020 г.

Захват аргументов

#cpp


Имеется класс Description, и его конструктор:

DescriptionWord::DescriptionWord (std::string translate, std::string    transcription,
PartOfSpech pos)
    : translation_{std::move(translate)}, transcription_{std::move(transcription)},
partOfSpech_{std::move(pos)} {
}


Будет ли создаваемый объект захватывать значения (точнее их копии), переданные в
конструктор при инициализации объекта? Или сдесь std::move не имеет смысла?
    


Ответы

Ответ 1



Разумеется std::move и семантика перемещения в таком контексте будет прекрасно работать для объектов, для которых они что-то дают, т.е. в данном случае для std::string. Аргументы после этого приобретут "moved-from" состояние, т. е. будут условно "разрушены". То что вы видите в данном примере - это как раз одна из рекомендуемых к использованию во многих случаях идиом: если вам в любом случае необходимо создать копию объекта, то лучше дать это сделать компилятору (т.е. использовать передачу по значению), а потом при необходимости выполнить перемещение из копии внутри метода. Таким способом вы одним определением метода покрываете почти все преимущества семантики перемещения: Если ваш метод будет вызываться с именованным объектом в качестве аргумента std::string str = "Hello World"; DescriptionWord a(str, ...); то компилятор выполнит копирование, а затем вы быстренько сделаете дешевое перемещение из копии. Если ваш метод будет вызываться с "исчезающим" объектом в качестве аргумента DescriptionWord a("Hello World", ...); то произойдет только ваше перемещение после конструкции аргумента, т.е. никакого копирования не будет. Если ваш метод будет вызываться с rvalue-ссылкой в качестве аргумента std::string str = "Hello World"; DescriptionWord a(std:::move(str), ...); то произойдет два перемещения (одно - компиляторное, другое - ваше), т.е. копирования не будет. Это не так эффективно, как пара конструкторов (один для lvalue ссылок, другой - для rvalue-ссылок) и это не так эффективно, как perfect forwarding аргументов, но это достаточно эффективно для большинства случаев и, еще раз, покрывает все ситуации одной реализацией. Если попытаться систематизировать доступные нам подходы к применению современных семантик перемещения и идеального форвардинга в вашей ситуации, то можно выделить четыре подхода Стиль С++98 - не используем семантику перемещения вообще Передача по константной ссылке и копирование внутри struct S { std::string s; S(const std::string &s) : s(s) // Вызывается конструктор копирования {} }; std::string s("Hello World"); S s1("Hello World"); // Конструкция временного объекта + копирование S s2(s); // Копирование S s3(std::move(s)); // Копирование Это - неэффективный подход, ибо он всегда делает копирование. Даже тогда, когда без него можно было бы запросто обойтись. "Ленивая" семантика перемещения Передача по значению и перемещение внутри struct S { std::string s; S(std::string s) : s(std::move(s)) // Вызывается конструктор перемещения {} }; std::string s("Hello World"); S s1("Hello World"); // Конструкция временного объекта + перемещение S s2(s); // Копирование + перемещение S s3(std::move(s)); // Перемещение + перемещение Это уже лучше. Копирование делается только тогда, когда оно действительно нужно. Незначительные недостатки этого варианта видны в сравнении со следующим вариантом. Развитая семантика перемещения Комбинируем 1 и 2 - предоставляем отдельные перегруженные методы на случай копирования и на случай перемещения struct S { std::string s; S(const std::string &s) : s(s) {} S(std::string &&s) : s(std::move(s)) {} }; std::string s("Hello World"); S s1("Hello World"); // Конструкция временного объекта + перемещение S s2(s); // Копирование S s3(std::move(s)); // Перемещение Это еще лучше. По сравнению с предыдущим вариантом, мы избавились от лишних перемещений для s2 и s3. Но зато нам пришлось писать два конструктора. Идеальный форвардинг Прямой форвардинг аргумента его конечному получателю struct S { std::string s; template S(A &&s) : s(std::forward(s)) {} }; std::string s("Hello World"); S s1("Hello World"); // Конструкция `S::s` напрямую S s2(s); // Копирование S s3(std::move(s)); // Перемещение Самый лучший вариант - не делается ничего лишнего. Однако теряется контроль типов через параметры конструктора. Чтобы вернуть его, придется предпринять некоторые усилия. Ваш пример следует второму варианту. Он самый простой и при этом пользующийся почти всеми преимуществами семантики перемещения. Его достаточно практически во всех случаях, когда из кода не нужно выжимать последние процессорные такты.

Ответ 2



Семантика перемещения будет иметь смысл, если вы хотите переместить владение ресурсом. Объект string, в упрощённом варианте, это указатель на динамический массив char и множество иных вспомогательных полей. В вашей реализации класса, например строка translate, после того как вы сделаете из нее перемещение при помощи метода move, будет содержать поля, КОТОРЫЕ БУДУТ ТАКИМИ, БУДТО ВЫ ВЫЗВАЛИ ДЛЯ translate КОНСТРУКТОР ПО УМОЛЧАНИЮ. А вот ресурс, на который указывал указатель в объекте translate, получит объект класса который вы создаёте. Т.е. поле создаваемого объекта класса будет владеть указателем, который будет указывать на массив чаров Иными словами: такой конструктор - персонаж из 90ых. Он занимается рэкетом и отбирает ресурсы у объектов, которые обернуты в move. Когда стоит заняться рэкетом: если вы не хотите копировать (захват, по вашему. Как я понял вы имеете ввиду что на динамический массив чаров будут указывать два объекта стринг. Так вот в этой ситуации такого не будет)

Комментариев нет:

Отправить комментарий