Господа, если не сложно, напомните мне пожалуйста, как мы поступаем в такой ситуации в этом нашем новом c++?
struct Bar {
void set_foos(const std::vector<Foo> &foos) {} // 1)
};
viod f()
{
std::vector<Foo> foos;
while (!done) {
if (cond1) {
Foo foo;
foo.load_from(file);
foos.push_back(foo);
}
if (cond2) {
cur_bar.set_foos(foos); // 2)
foos.clear();
}
}
}
Я хочу чтобы содержимое вектора foos премещалось в cur_bar, а не копировалось
И при этом чтобы foos очищался и его можно было продолжать использовть
Какой интерфейс должен быть в 1) и что делать в 2) ?
Здравствуйте, borya_ilin, Вы писали:
_>Я хочу чтобы содержимое вектора foos премещалось в cur_bar, а не копировалось _>И при этом чтобы foos очищался и его можно было продолжать использовть _>Какой интерфейс должен быть в 1) и что делать в 2) ?
Если речь об оптимизации по скорости, то замена set_foos(const std::vector<Foo> &foos) на swap_foos(std::vector<Foo>& foos) { member.swap(foos); ... } будет, ИМХО, наилучшей.
_>Я хочу чтобы содержимое вектора foos премещалось в cur_bar, а не копировалось _>И при этом чтобы foos очищался и его можно было продолжать использовть
_>Какой интерфейс должен быть в 1)
struct Bar {
void set_foos(const std::vector<Foo> &foos) {} // 1)void set_foos(std::vector<Foo> &&foos){ //1a. Эти две перегрузки могут существовать одновременно
m_foos = std::move(foos); // Использование move принциаиально - без него будет копирование, а не перемещение
}
private:
std::vector<Foo> m_foos;
};
Если set_foos делает что-то более сложное, чем перемещающее присваивание, то тут уже нужно смотреть отдельно.
_>и что делать в 2) ?
cur_bar.set_foos(std::move(foos)); // 2)
foos.clear(); // Это нужно для гарантии, что foos пустой.
Если set_foos выполняет перемещение содержимого foos, то вызов clear не является необходимым. Но мы ведь можем и не знать, что там делается внутри set_foos, поэтому лучше вызвать clear, во избежание "сюрпризов".
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, borya_ilin, Вы писали:
_>>Я хочу чтобы содержимое вектора foos премещалось в cur_bar, а не копировалось _>>И при этом чтобы foos очищался и его можно было продолжать использовть _>>Какой интерфейс должен быть в 1) и что делать в 2) ?
BFE>Если речь об оптимизации по скорости, то замена set_foos(const std::vector<Foo> &foos) на swap_foos(std::vector<Foo>& foos) { member.swap(foos); ... } будет, ИМХО, наилучшей.
Ну, с учетом того, что старое содержимое никому не нужно, и контейнер все равно будет очищаться, можно без потерь эту очистку поместить внутрь функции, что позволит сохранить ее семантику и имя без изменений. И что там будет внутри делаться — swap или перемещение — это уже детали реализации самой функции.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Ну, с учетом того, что старое содержимое никому не нужно, и контейнер все равно будет очищаться, можно без потерь эту очистку поместить внутрь функции, что позволит сохранить ее семантику и имя без изменений. И что там будет внутри делаться — swap или перемещение — это уже детали реализации самой функции.
Насколько я помню, большинство реализаций вектора не отказываются от аллокированной памяти при удалении элементов (даже shrink_to_fit() не даёт гарантий), поэтому организовав ping-pong можно выиграть (и нельзя проиграть) на количестве аллокаций памяти.
Здравствуйте, borya_ilin, Вы писали:
_>Господа, если не сложно, напомните мне пожалуйста, как мы поступаем в такой ситуации в этом нашем новом c++?
_>
_> foos.push_back(foo);//3
_>
_>Я хочу чтобы содержимое вектора foos премещалось в cur_bar, а не копировалось _>И при этом чтобы foos очищался и его можно было продолжать использовть _>Какой интерфейс должен быть в 1) и что делать в 2) ?
Можно и от этого избавиться, например так
Здравствуйте, B0FEE664, Вы писали:
R>>Ну, с учетом того, что старое содержимое никому не нужно, и контейнер все равно будет очищаться, можно без потерь эту очистку поместить внутрь функции, что позволит сохранить ее семантику и имя без изменений. И что там будет внутри делаться — swap или перемещение — это уже детали реализации самой функции.
BFE>Насколько я помню, большинство реализаций вектора не отказываются от аллокированной памяти при удалении элементов (даже shrink_to_fit() не даёт гарантий), поэтому организовав ping-pong можно выиграть (и нельзя проиграть) на количестве аллокаций памяти.
Так и есть. Но при этом совсем не обязательно выставлять swap наружу и менять имя и семантику функции:
struct Bar {
void set_foos(std::vector<Foo> &&foos) {
swap(m_foos, foos);
foos.clear(); // элементы удаляются, от памяти не отказываемся, пинг-понг жив (с)
}
private:
std::vector<Foo> m_foos;
};
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, rg45, Вы писали:
R>>Так и есть. Но при этом совсем не обязательно выставлять swap наружу и менять имя и семантику функции:
R>>
R>>struct Bar {
R>> void set_foos(std::vector<Foo> &&foos) {
R>> swap(m_foos, foos);
R>> foos.clear(); // элементы удаляются, от памяти не отказываемся, пинг-понг жив (с)
R>> }
R>>private:
R>> std::vector<Foo> m_foos;
R>>};
R>>
BFE>1. Такое поведение неожиданно.
Какое "такое"? Функция называется set_foos (а не swap_foos) и производимый ею эффект в точности соответствует ее названию и семантике.
BFE>2. Зачем тут &&? Достаточно простой ссылки.
А вот это как раз будет неожиданно, когда foos вдруг лишится своих элементов в таком сценарии:
Bar bar;
std::vector<Foo> foos {/*. . . */};
bar.set_foos(foos);
Ты не замечаешь, что мы с тобой говорим про две разные функции? Я говорю про исходную set_foos, а ты ее уже мыслено зарефакторил, наделил ее другой семантикой и смотришь на нее как на swap_foos.
Давай я еще раз сформулирую свою позицию: для того, чтобы достичь оптимизации по скорости, о которой ты говоришь здесь
Здравствуйте, rg45, Вы писали:
BFE>>1. Такое поведение неожиданно. R>Какое "такое"?
вызов выглядит так:
bar.set_foos(std::move(foos));
Я ожидаю, что после этого foos не владеет никакой динамической памятью. Впрочем, быть может, это мои проблемы.
BFE>>2. Зачем тут &&? Достаточно простой ссылки. R>А вот это как раз будет неожиданно, когда foos вдруг лишится своих элементов в таком сценарии: R>
Тут я согласен, так делать не следует.
Меня смущает другое: семантика перемещения используется не понятно зачем. Какое же это перемещение?
Ещё меня смущает то, что объект используется после того, как его переместили, но это мелочи.
R>Ты не замечаешь, что мы с тобой говорим про две разные функции? Я говорю про исходную set_foos, а ты ее уже мыслено зарефакторил, наделил ее другой семантикой и смотришь на нее как на swap_foos.
Ну это ещё как посмотреть... По мне это у вас поменялась семантика set_foos без изменения имени функции.
Я не против добавления функции set_foos(std::vector<Foo> &&foos) (или, быть может, set_foos(auto&& foos)), но только с ожидаемым поведением.
R>Давай я еще раз сформулирую свою позицию: для того, чтобы достичь оптимизации по скорости, о которой ты говоришь здесь
BFE>Тут я согласен, так делать не следует. BFE>Меня смущает другое: семантика перемещения используется не понятно зачем. Какое же это перемещение?
В этом примере семантика перемещения как раз НЕ используется. И в этом случае должна быть выбрана копирующая версия функции set_foos. По семантике две эти перегрузки очень близки к копирующему и перемещающему операторам присваивания. Кстати, перемещающй оператор присваивания вполне может быть реализован при помощи swap, с сохраниением оптимизации "пинг-понг", как ты метко ее окрестил.
BFE>Ещё меня смущает то, что объект используется после того, как его переместили, но это мелочи.
Вот с этим аргументом соглашусь, пожалуй. Но это отдельный холивар. Мы тут уже копья ломали на эту тему, помнится.
R>>Ты не замечаешь, что мы с тобой говорим про две разные функции? Я говорю про исходную set_foos, а ты ее уже мыслено зарефакторил, наделил ее другой семантикой и смотришь на нее как на swap_foos.
BFE>Ну это ещё как посмотреть... По мне это у вас поменялась семантика set_foos без изменения имени функции.
Тот же принцип, по которому одновременно существуют копирующая и перемещающая версии оператора присваивания, ничего неожиданного.
BFE>Я бы не стал переделывать, я бы просто добавил бы функцию swap_foos.
А я бы не стал добавлять ненужные функции.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
BFE>>Ну это ещё как посмотреть... По мне это у вас поменялась семантика set_foos без изменения имени функции. R>Тот же принцип, по которому одновременно существуют копирующая и перемещающая версии оператора присваивания, ничего неожиданного.
Я ожидаю, что после выполнения этого кода:
bar.set_foos(std::move(foos));
у переменной foos не останется динамически аллокированной памяти.
Т.е. я ожидаю, что следующие постусловие метода set_foos(std::vector<Foo>&&) будет соблюдатся:
Здравствуйте, B0FEE664, Вы писали:
BFE>Я ожидаю, что после выполнения этого кода: BFE>
BFE>bar.set_foos(std::move(foos));
BFE>
BFE>у переменной foos не останется динамически аллокированной памяти.
С чего вдруг? Ты передал объект по rvalue ссылке, разрешив обращаться с его содержимым как угодно. КАК УГОДНО, понимаешь? Функция может переместить это содержимое, а может скопировать. А может подсунуть туда старое содержимое инкапсулированного поля при помощи swap. Эта функция ничего тебе не обещает сверх того, что заложено в ее семантике. Вот если бы она называлась move или swap, тогда другое дело.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
BFE>>Я ожидаю, что после выполнения этого кода: BFE>>
BFE>>bar.set_foos(std::move(foos));
BFE>>
BFE>>у переменной foos не останется динамически аллокированной памяти.
R>С чего вдруг? Ты передал объект по rvalue ссылке, разрешив обращаться с его содержимым как угодно. КАК УГОДНО, понимаешь? Функция может переместить это содержимое, а может скопировать. А может подсунуть туда старое содержимое инкапсулированного поля при помощи swap.
Это я прекрасно понимаю и принимаю. Меня не смущает, что операция деления может складывать аргументы, а цикл может использоваться как goto, но у того, кто первый раз читает такой код, будут вопросы к автору. Если трюк обоснован и красив, то его можно принять и использовать. Однако, как правило, трюки со скрытыми и неочевидными эффектами не приживаются.
R>Эта функция ничего тебе не обещает сверх того, что заложено в ее семантике. Вот если бы она называлась move или swap, тогда другое дело.
В том-то и дело, что в вызове я вижу семантику перемещениям (std::move как бы намекает), поэтому я ожидаю, что это и есть перемещение, а не что-то ещё.
Здравствуйте, borya_ilin, Вы писали:
_>вообще норм, но не очень хотелось бы чтобы реализация начала интерфейсом рулить
У std::vector swap в интерфейсе есть, однако.
Впрочем, быть может, swap — неудачное наименование метода. Как насчёт exchange_foos(std::vector<Foo>& foos)? Вроде бы звучит солидно, по корпоративному.
Здравствуйте, B0FEE664, Вы писали:
R>>Эта функция ничего тебе не обещает сверх того, что заложено в ее семантике. Вот если бы она называлась move или swap, тогда другое дело. BFE>В том-то и дело, что в вызове я вижу семантику перемещениям (std::move как бы намекает), поэтому я ожидаю, что это и есть перемещение, а не что-то ещё.
А чего ты ждешь, например, от перемещающего оператора присваивания std::vector? Если также гарантированного перемещения, то совершенно напрасно — все, чего требует стандарт — это оставить объект "in valid but unspecified state". И если ты полистаешь стандарт, то увидишь, что это не какой-то исключительный случай, а обычная практика. Этому в стандарте посвящено даже несколько отдельных параграфов.
--
Не можешь достичь желаемого — пожелай достигнутого.