LINUX.ORG.RU

copy-and-swap

 


0

3

Изучаю C++, читаю книгу Hands-on Design Patterns with C++, главу Swap and exception safety. Там есть такой пример:

class C; // Our element type
C transmogrify(C x) { return C(...); }    // Some operation on C
void transmogrify(const std::vector<C>& in, std::vector<C>& out) {
    out.resize(0);
    out.reserve(in.size());
    for (const auto& x : in) {
        out.push_back(transmogrify(x));
    }
}

Написано, что push_back может выкинуть exception и наш вектор out останется пустым или неполным. Предлагается такое решение:

void transmogrify(const std::vector<C>& in, std::vector<C>& out) {
    std::vector tmp;
    tmp.reserve(in.size());
    for (const auto& x : in) {
        tmp.push_back(transmogrify(x));
    }
    out.swap(tmp); // Must not throw!
}

Якобы swap() не выбросит исключения и теперь у нас есть strong exception guarantee и весь этот финт ушами называется идеомой copy-and-swap. Только вот я не понимаю, чем именно обусловлена стронк гарантия? Функция все так же может выбросить исключение и вызывающая функция все равно должна проверить вектор out на валидность, хотя бы размеры in и out сверить.

Ответ на: комментарий от hibou

Имеются ввиду ситуации, когда использование данных неполного массива вредит ситуации. Никаких дополнительных проверок на размер в этом случае не нужно. Если массив будет пустым, то просто не будет использоваться, без лишней проверки – просто в for будет достигнут конец.

hibou ★★★★★
()

тут суть в том, что если исключение вылетело, то твой out находится именно в том же состоянии, что и до вызова функции (нету состояния «недозаполнености», либо в точности что ты туда передал, либо полностью заполненный)

Stil ★★★★★
()

чем именно обусловлена стронк гарантия? Функция все так же может выбросить исключение

Strong guarantee это про то что у тебя никакое видимое клиентскому коду состояние при выкидывании исключения не изменилось. Здесь эта гарантия соблюдается.

вызывающая функция все равно должна проверить вектор out на валидность, хотя бы размеры in и out сверить.

Нет, не должна. Её может интересовать только было ли исключение, потому что при strong guarantee состояние out известно - если было исключение оно осталось таким как было до вызова.

slovazap ★★★★★
()

void transmogrify(const std::vector& in, std::vector& out) {

Представим, что мы объявляем в одной строке ссылку на целочисленный тип и переменную целого типа:

int &a, b;

То есть если ставить пробел после типа переменной, то сразу становится ясно где объявлена ссылка, а где - сама переменная.

Если ты видишь, что писатель даже указатели и ссылки не смог осмыслить и понять, то чему он сможет научить в своей книге? - Время и силы свои потратишь да мышление испортишь только. Выкидывай эту книгу в мусорку и бери другую.

Enthusiast ★★★
()
Ответ на: комментарий от Enthusiast

Просто не объявляй несколько переменных в одной строке и у тебя не будет проблем с пониманием их типа. И сможешь нормально описывать тип отдельно от имени переменной, а не придумывать, какой очередной хернёй оправдать то, что у тебя часть типа прилеплена к имени переменной.

Ivan_qrt ★★★★★
()

Книжка с какими-то устаревшими паттернами. Нафига делать out параметр, а потом свопить в него, когда можно просто возвратить по значению?

auto transmogrify(const std::vector<C>& in) {
    std::vector tmp;
    tmp.reserve(in.size());
    for (const auto& x : in) {
        tmp.push_back(transmogrify(x));
    }
    return tmp;
}

Здесь вообще даже не будет вызова move конструктора, потому что применится NRVO.

rupert ★★★★★
()
Последнее исправление: rupert (всего исправлений: 1)
Ответ на: комментарий от Ivan_qrt

Просто не объявляй несколько переменных в одной строке и у тебя не будет проблем с пониманием их типа.

То есть ты предлагаешь вместо изучения правил русского языка просто не использовать в своей письменной речи слова, написания которых не знаешь? Если так рассуждать, тогда гораздо разумнее отбросить Си и Си++, а учить языки без ссылок и указателей.

Именно способность осваивать неведомое отличает истинного изобретателя от глупого повторятеля.

Enthusiast ★★★
()
Ответ на: комментарий от Enthusiast

То есть ты предлагаешь вместо изучения правил русского языка просто не использовать в своей письменной речи слова, написания которых не знаешь?

Во-первых, причём тут я, если проблемы с пониманием были у тебя?
Во-вторых, нет. Попробуй перечитать сообщение, на которое отвечаешь и осмыслить написанное, возможно всё-таки получится.

тогда гораздо разумнее отбросить Си и Си++, а учить языки без ссылок и указателей.

Я не возражаю, можешь отбрасывать.

Ivan_qrt ★★★★★
()
Ответ на: комментарий от Enthusiast

Не понял твоего посыла, но в целом ты прав, Fedor Pikus идиот и книги у него тупые. Целый набор абсолютно дегенеративных примеров запартной обезьяны, которая вообще не понимает что делает, и героические пути их решения, актуальные разве что для джуна компании Microsoft с C++03 через плечо.

Предостеречь хватит - держитесь подальше от данного оратора, если хотите уметь в C++.

naushniki
() автор топика
Ответ на: комментарий от naushniki

Не понял твоего посыла, но в целом ты прав, Fedor Pikus идиот и книги у него тупые.

Похоже на то, что ты происходишь из околонаучной среды и поэтому восторгаешься путем Федора. Отучиться бесплатно в Политехе, понабраться опыта в институте Йоффе и сбежать в Америку, чтобы там устроиться в университет поначалу, а затем расчетчиком-программистом моделирующего ПО в богатую контору.

Только промах Федора заключается в том, что его, как «колбасного» переселенца, никогда не допустят до настоящего дела, связанного с оборонной промышленностью, и будет Федор бегать на побегушках со своими расчетами перед американцами, как мальчик, до самой старости.

Возможно, Федор достиг в физике многого со своим открытием квантовых точек, но его же собственные слова, что русский язык является для него иностранным лишь подтверждают мои слова.

Enthusiast ★★★
()
Ответ на: комментарий от rupert

Не обращай внимания. Как я понимаю, изначальный посыл был просто продемонстрировать идиому. В варианте с out параметром её просто лучше видно.

Но вот NVRO будет не всегда. Я смотрел некоторые реализации и NVRO выглядело как раз передача дополнительного параметра, ну или предварительная аллокация на стеке вызывающим кодом (ну собственно так и работает). Т.е. в таком примере:

vector v = ...;
v = transmogrify(v);

предполагаю, что move конструктор всё равно вызовется. Но код по прежнему останется exception safe, если исключение случится внутри transmogrify().

Плюс про «Guaranteed Copy Elision» я помню только в C++17, и то там есть нюансы.

Но в целом да, я бы писал как ты показал, так как:

  1. Если получается NVRO - мы в шоколаде
  2. Если не получается - move, что тоже неплохо и максимум от того, что мы могли бы сделать ручками
  3. Совсем плохо (нет move конструктора) - ну копия, ок, но и руками бы мувнуть мы бы не смогли.
  4. Более того, код будет работать, пусть и не так хорошо, если move конструктора нет (см предыдущий пункт).
hatred ★★★
()

Изучаю C++, читаю книгу Hands-on Design Patterns with C++, главу Swap and exception safety. Там есть такой пример...

Я дико извиняюсь, но кто-то до сих пор применяет исключения в C++ за пределами Qt/UE/legacy? Я вот уже полтора года как работаю на крестовом проекте, и всё, что узнал про исключения за это время — это что их нужно немедленно обрабатывать, чтобы избегать сложной скрытой логики выполнения. Да, RAII кое-где применяем для упрощения логики return, хотя я бы поубирал его в функциях более 20 строк, где объявление какого-нибудь lock_guard-а не видно при чтении низа функции.

Отсутуствие деления этапов деструкции на деинициализацию и переиспользование памяти приводит к тому, что в сложных случаях деструкторы просто-напросто недопустимо вызывать из-за провисающих ссылок — всё это RAII с исключениями становится сплошной непредсказуемой лапшой за пределами книжных примеров. А в книжках-то работает! Я прямо-таки представил совещание на дедлайне, где тимлид рапортует «система регулярно падает, но зато книжку про нее я написал».

byko3y ★★★★
()
Ответ на: комментарий от Enthusiast

Если ты видишь, что писатель даже указатели и ссылки не смог осмыслить и понять, то чему он сможет научить в своей книге?

Я когда увидел, как в C++ так и не научились четко делать разграничение между ссылкой, указателем, и массивом, то подумал «язычок еще сырой, как допилят, то посмотрю еще раз» — и пошел писать на делфях, где синтаксис типов не представляет собой кашу из char* (&arr)[N].

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Я дико извиняюсь, но кто-то до сих пор применяет исключения в C++ за пределами Qt/UE/legacy?

А что, в Qt начали исключения применять?

Отсутуствие деления этапов деструкции на деинициализацию и переиспользование памяти приводит к тому, что в сложных случаях деструкторы просто-напросто недопустимо вызывать из-за провисающих ссылок — всё это RAII с исключениями становится сплошной непредсказуемой лапшой за пределами книжных примеров.

"– Доктор, когда я делаю вот так, то мне больно!

– А вы не делайте так." (с)

А в книжках-то работает!

Оно и не в книжках работает.

Я прямо-таки представил совещание на дедлайне, где тимлид рапортует «система регулярно падает, но зато книжку про нее я написал».

Вы явно думаете не о том, поэтому-то у вас «в сложных случаях деструкторы просто-напросто недопустимо вызывать из-за провисающих ссылок»

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Какого легаси: с использованием исключений или нет?

Qt, скомпилированный без поддержки исключений, при той же ошибке выделения памяти уходит в UB. Потому, очевидно, это легаси с исключениями.

В мифических «сложных случаях»?

Не «мифических», а вполне себе обычных случаях какой-нибудь adobe-поделки или очередной игори в эпоху до массового нашествия Unity.

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Qt, скомпилированный без поддержки исключений, при той же ошибке выделения памяти уходит в UB. Потому, очевидно, это легаси с исключениями.

Кому это очевидно? Вам? Ну тогда-то все понятно.

Qt написано без поддержки исключений. Ну вот вообще.

Так что причислять Qt к «легаси с исключениями» можно разве что подразумевая под «исключениями» что-то вроде «исключения из правил», а не механизм исключений в языке C++.

Не «мифических», а вполне себе обычных случаях какой-нибудь adobe-поделки или очередной игори в эпоху до массового нашествия Unity.

Я подозреваю, что никаких описаний самих этих сложных случаев и примеров возникающих ошибок не будет. Ведь так?

eao197 ★★★★★
()
Ответ на: комментарий от byko3y

«система регулярно падает, но зато книжку про нее я написал»

А я писателей книг по best practices только так и вижу. Еще со времен Си помню эти убогие до невозможности примеры, где всё ломается и героические пути их решения «как надо». Только вот нормальному человеку и в голову не придет писать как ненадо изначально. Вот бы книги по физике так писали - Вася прикрутил 50 вольт к 50 ампер и сжог все нафиг. Пришел Петя и рассказал что ампер надо 49.

naushniki
() автор топика

Только вот я не понимаю, чем именно обусловлена стронк гарантия?

Тут интереснее понять почему вы такие элементарные вещи не понимаете. Ведь пример же очевиден. И суть, судя по вашей цитате, вы должны были схватить – «Якобы swap() не выбросит исключения и теперь у нас есть strong exception guarantee…»

Так что именно вам непонятно? Может ли swap бросить исключение?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

А что тут непонятного? Где exception guarantee? Почему это недоразумение назвали «идеома»? Нормальный код все равно проверит вектор out на валидность, так или иначе. Мне непонятен подход и почему это раздувают до best practice.

naushniki
() автор топика
Ответ на: комментарий от naushniki

Где exception guarantee?

Потому что все исключения происходят до вызова swap.

Если вам все еще непонятно, то попробуйте проделать следующее упражнение: возьмите приведенный вами пример функции transmogrify и пройдитесь по каждой ее строке задавая себе вопрос «что произойдет с in и out если здесь выскочит исключение?»

void transmogrify(const std::vector<C>& in, std::vector<C>& out) {
    std::vector tmp;
    tmp.reserve(in.size());
    for (const auto& x : in) {
        tmp.push_back(transmogrify(x));
    }
    out.swap(tmp); // Must not throw!
}

Вот буквально по каждой строке.

Почему это недоразумение назвали «идеома»?

Может быть потому, что это не «недоразумение»?

Нормальный код все равно проверит вектор out на валидность, так или иначе.

И как «нормальный код» будет проверять вектор out на валидность, по вашему?

Мне непонятен подход и почему это раздувают до best practice.

ИМХО, ключевое здесь «мне непонятен подход». Как только вы с этим разберетесь все остальное должно встать на свои места.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Qt написано без поддержки исключений. Ну вот вообще.

В 1996 году? Да. Я проверил мой QtCreator — собран с исключениями, Q_CHECK_PTR бросает std::bad_alloc при исчерпании памяти. Другое дело, что таки более старая модель у него без исключений — согласен, пример ужасный. Но я же не мог сказать «старый проект на STL» — это было бы что-то максимально абстрактное.

Я подозреваю, что никаких описаний самих этих сложных случаев и примеров возникающих ошибок не будет. Ведь так?

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

Конкретные проблемы на самом деле очень легко найти и они почти что лежат в фундаменте сишных библиотек. Например:
https://github.com/gcc-mirror/gcc/blob/9bd194434acb47fac80aad45ed04039e0535d1...
Вопрос: что происходит с еще не удаленными элементами, если в процессе clear() деструктор одного из элементов вызывает функцию с побочными эффектами, которые потенциально обращаются к родительскому std::list, прямо (например, через некую ссылку std::list *owner) или косвенно.

Пример не ограничивается std::list, его можно применить к любому контейнеру STL или просто рукописными взаимным ссылкам — внешне неопределенное состояние во время и после деструкции является нормой для C++. Именно потому, например, автор ZeroMQ отказался от сложных деструкторов в пользу пары init()/term().

byko3y ★★★★
()
Последнее исправление: byko3y (всего исправлений: 1)
Ответ на: комментарий от byko3y

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

Позиция пока что такая: вы сказали откровенную херню.

Вопрос: что происходит с еще не удаленными элементами, если в процессе clear() деструктор одного из элементов вызывает функцию с побочными эффектами, которые потенциально обращаются к родительскому std::list, прямо (например, через некую ссылку std::list *owner) или косвенно.

Вы еще спросите, что происходит вот в таких случаях: i = ++i + i++.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Позиция пока что такая: вы сказали откровенную херню.

Позиция ясна, но она не конкретна.

Вы еще спросите, что происходит вот в таких случаях: i = ++i + i++.

Без выражений с префиксными-постфиксными операциями можно жить, а вот как жить без возможности реализовать двунаправленное управление «родительский->дочерний» и «дочерний->родительский» — не совсем ясно. Или предлагаешь, как в хаскеле, переписывать половину архитектуры каждый раз, когда возникает необходимость нестандартного потока управления?

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Позиция ясна, но она не конкретна.

Она вполне конкретна.

Если у вас есть нормальные вопросы по сценариям использования исключений, то задавайте. Возможно, кто-то сочтет возможным вам ответить. А вот комментировать поток сознания в виде «я уже полтора года пишу на плюсах и вижу, что все не так однозначно» можно разве что в стиле АМ/КГ.

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

охоспядя, что еще за «нестандартный поток управления» :facepalm: :facepalm: :facepalm:

Такое ощущение, что вы в плену каких-то стереотипов, вроде «деструктор – это волшебная пилюля», и вам очень больно, когда объективная реальность прикладывает вас фейсом об тейбл не взирая на ваши розовые очки.

Так что еще раз: без озвучивания конкретных сценариев и конкретных проблем разговор возможен только о степени вашей нетрадиционной ориентации в области программирования.

eao197 ★★★★★
()