Comments 94
С++ очень сложный. И очень многогранный.
Он реально крутой… Наверное. Но писать на нем не буду. Слишком велик риск остаться инвалидом.
зависит от реализации GC конечно
GC справляется с циклическими ссылками. Для этого он и сделан — удалять весь мусор, на который нет активных ссылок. ARC — это не сборщик мусора. Там или нет мусора (если нет циклических ссылок), или мусор не удаляется (если они есть).
Не буду использовать циркулярную пилу, буду использовать канцелярский нож. Им я хотя бы не отпилю себе руку.
C++ дает контроль на всех уровнях над кодом. Да, он далеко не идеален, и за такой контроль приходится расплачиваться целым букетом недостатков (а так как он дает еще и обратную совместимость (в т.ч. с С), то там еще букет расплаты приходит, и это все перемножается в немыслимых комбинациях).
Ваша мотивация понятна, но в основном С++ избегают не из-за страха инвалидности.
Вопрос совместимости с легаси переоценен, как и преувеличена совместимость с++ с с и более старыми версиями стандарта с++
Кратко — код не соберётся, а если даже соберётся, то не факт, что будет работать так же
Лол. Секрет в том, что эти проекты адаптируются в каждый конкретный момент под актуальную версию компилятора и стандарта. И нет шоковой терапии, когда мы берём код десятилетней давности и пытаемся его адаптировать.
Так что — нет, я с Вами не согласен.
А знаете, что я в этом наблюдаю? Что С++ остается в достаточно узком сегменте, где он отлично подходит. Т.е. это не истории про веб-сервисы, про сетевые монолиты, от которых отпиливают куски и переводят на более другие языки, не про распределенные сервисы, не про микроконтроллеры и встройку.
Конечно, я знаю, что про проекты вроде Scylladb (drop-in замена Cassandra) — но это частные случаи.
И это я ещё не упоминал проектирование вашей квартиры, выдачу вам зарплаты, лечение в больничках, дальние путешествия, дизайн всего подряд, кино, мультфильмы, научные исследования…
Но да, сфера узковата, надо расширять. Тут я согласен
Это десктоп приложение
Точно так же никто не отрицает, что игры до сих пор по определённым причинам пишут на плюсах.
И, да, я читал статьи про сборку хрома. Это тотал боль. И тестирование тоже.
Ещё раз. Это вполне подходит под "узкий сегмент, где с++ отлично выполняет свою работу". Вы ведь правда читали, что я написал тремя постами выше?
И Вы же согласны, что не каждый Васян пишет свой браузер. Основных — полтора штука, которые есть на каждой платформе.
Лол. Секрет в том, что эти проекты адаптируются в каждый конкретный момент под актуальную версию компилятора и стандарта. И нет шоковой терапии, когда мы берём код десятилетней давности и пытаемся его адаптировать.1. код надо писать переносимым, а не так, чтобы приходилось под каждый компилятор адаптировать
2. бывает пишешь новый код, а ему нужны старые библиотеки.
А знаете, что я в этом наблюдаю? Что С++ остается в достаточно узком сегменте, где он отлично подходитплохо наблюдаете. с++ в той или иной мере применим везде, для чего существуют с++ компиляторы, т.е. за исключением совсем непопулярного ембеда. Его распространенность действительно легко недооценить если забывать про то, что у всяких питончиков и джав под капотом плюсы.
Это отличная история, когда плюсовики присваивают себе наработки Сишников.
Линукс, Windows (последний раз я смотрел на исходники 2000 ядро + драйвера + немного юзерспейса) — все на Сях.
Касательно питончика — Вы имели в виду либы или сам интерпретатор? Ну, так и ТО, и ТО как правило на Сях, а не на С++ и Вы сами прекрасно знаете почему это так (бинарная совместимость, манглинг и прочая-прочая).
В качестве пруфа — https://launchpad.net/~jonathonf/+archive/ubuntu/python-3.7/+sourcefiles/python3.7/3.7.4-2~18.04.york0/python3.7_3.7.4.orig.tar.xz — вот пакет, из которого собирается альтернативный 3.7 питон к убунте. Можно точно так же открыть и оригинальный пакет. С++ там и не пахнет.
Java — положим, сама виртуальная машина. Очень даже интересно посмотреть. Только давайте договоримся о какой имплементации мы говорим, ок?
Линукс, Windowsа вы под «Линукс» имеете в виду ядро, застрявшее на си в силу фанатизма Торвальдса, или всю ось? А винду вы оцениваете какой код? Открытый?
Касательно питончика — Вы имели в виду либы или сам интерпретатор? Ну, так и ТО, и ТО как правило на Сях, а не на С++не горячился бы я с «как правило». Взять тот же opencv — они отказались от сишного интерфейса, попросту потому что неразумно. И если вы попробуете скажем оформить питонячий модуль на плюсах и на чистом си, вы прекрасно поймете почему.
… и Вы сами прекрасно знаете почему это так (бинарная совместимость, манглинг и прочая-прочая).внезапно плюсы умеют не только вызывать сишные функции, но и так же просто определять их.
Java — положим, сама виртуальная машина. Очень даже интересно посмотреть. Только давайте договоримся о какой имплементации мы говорим, ок?а о какой существующей имплементации мы говорим?
а вы под «Линукс» имеете в виду ядро, застрявшее на си в силу фанатизма Торвальдса, или всю ось?
Вся ось — там такое сборище… есть ВСЕ — начиная от С, кончая перлом. Да, есть код на С++ (в первую очередь — граф. приложения). Но сказать, что он "системообразующий"… кхм… слишком смелое утверждение.
А винду вы оцениваете какой код? Открытый?
Это не имеет значения. Кто хочет — тот может посмотреть (тем более были утечки кодов NT & W2k или можно по академ подписке).
внезапно плюсы умеют не только вызывать сишные функции, но и так же просто определять их.
в обратную сторону все сильно сложнее.
а о какой существующей имплементации мы говорим?
Это к Вам вопрос. Ибо даже школьнику известно, что есть несколько разных JVM....
Прям вдохновило пойти и проверить те места, где я использовал shared_from_this.
Ну и очень порадовал ответ на самый распространенный обычный вопрос «так никто не пишет! по крайней мере в библиотеках написанных профи!»
Не вижу тут ничего специфичного для enable_shared_from_this, обыкновенная циклическая ссылка же. Я такое могу и без enable_shared_from_this сделать.
Но оформлена статья хорошо, спору нет.
Статья — о циклических ссылках, в которые попадают активные сущности.
Моя практика (да и примеры стороннего кода) показывает, что обычно циклическая ссылка возникает всё же при помощи std::enable_shared_from_this.
И я бы даже сильнее сказал: если Вам захотелось задействовать std::enable_shared_from_this — вероятно, Вам надо чинить архитектуру.
Не каждое место в C++-коде, требующее повышенного внимания, вызвано применением std::enable_shared_from_this. Но каждое место применения std::enable_shared_from_this требует повышенного внимания.
Я бы сформулировал по-другому. Всякая операция захвата shared_ptr в лямбде требует повышенного внимания.
Точно такого же эффекта я добьюсь, если напишу подобный класс руками. Только это более многословно.
А в примерах SimpleCyclic и PimplCyclic operator() всё равно не используется, т.е. достаточно только конструктора и поля данных.
Как собираетесь такие места выделять в коде для обращения на них повышенного внимания?
К сожалению, для valgrind или ASAN тут практически нет шансов. С их точки зрения всё отлично до последнего момента, т. к. память не утекла и находится у другого потока. И по завершению detached thread зачищается системой. При этом в нём самом тоже толком ничего не утекло.
Кроме того, аналогичного поведения можно добиться вообще без вызова detach и даже без запуска потока в пользовательском коде. Примеры из документации буста легко переделать под такое — надо только не блокировать главный поток вызовом run(), а запустить в нём polling-loop с вызовами boost::asio::io_context::poll().
Впрочем, практически любое использование shared_from_this можно переписать через pimpl.
class MyClass {
... // copyable, movable, etc. handle to data
private:
class MyClassPrivate;
std::shared_ptr<MyClassPrivate>;
};
Если Вы имеете в виду, что при таком построении пимпла фасад может подсунуть реализации ссылку на неё же саму — то да, может. И получится то же самое, что с std::enable_shared_from_this. Это ситуация типа «захотелось применить std::enable_shared_from_this, но не знали о таком, поэтому сделали то же самое, но без перламутровых пуговиц». И проверить будет сложнее, т.к. поиск по shared_from_this не покажет такого места.
Антипаттерн «Зомби» — не просто про управление классом собственным временем жизни. Это ещё цветочки, которые могут работать корректно.
Пример SteppingZomby в статье отработал так:
N13SteppingZomby5ZombyE::resolveDnsName started
N13SteppingZomby5ZombyE::resolveDnsName finished
N13SteppingZomby5ZombyE::connectTcp started
============================================================
| Zomby was killed |
============================================================
N13SteppingZomby5ZombyE::connectTcp finished
N13SteppingZomby5ZombyE::establishSsl started
N13SteppingZomby5ZombyE::establishSsl finished
N13SteppingZomby5ZombyE::sendHttpRequest started
N13SteppingZomby5ZombyE::sendHttpRequest finished
N13SteppingZomby5ZombyE::readHttpReply started
N13SteppingZomby5ZombyE::readHttpReply finished
N13SteppingZomby5ZombyE::~Zomby
N6Common22WriteToConsoleListenerE::~WriteToConsoleListener
А мог бы отработать вот так:
N13SteppingZomby5ZombyE::resolveDnsName started
N13SteppingZomby5ZombyE::resolveDnsName finished
N13SteppingZomby5ZombyE::connectTcp started
============================================================
| Zomby was killed |
============================================================
N13SteppingZomby5ZombyE::connectTcp finished
N13SteppingZomby5ZombyE::~Zomby
N6Common22WriteToConsoleListenerE::~WriteToConsoleListener
Управление собственным временем жизни могло быть в обоих случаях. Но иногда оно создаёт весьма сложные для детектирования и отладки проблемы, а иногда — нет.
Т.е. «Зомби» — это когда уже «доуправлялись собственным временем жизни». Когда абстрактная кривизна архитектуры превратилась в большущие, но при этом хорошо замаскированные проблемы. Вроде код работает. И автотесты проходят. И динимаческий анализ молчит. И статический тоже молчит. Ну может в логах оно отбивается (но если отбивается — значит логи многокилометровые, и следы зомби там могут теряться годами).
Ведь в примерах SimpleCyclic и PimplCyclic тоже есть управление собственным временем жизни. Но это не то, оно не так опасно — и динамическим анализом находится, и на практике я не знаю ситуаций, в которых захотелось бы так написать.
А для активных сущностей — иногда именно так и пишут.
Если Вы имеете в виду, что при таком построении пимпла фасад может подсунуть реализации ссылку на неё же саму — то да, может. И получится то же самое, что с std::enable_shared_from_this. Это ситуация типа «захотелось применить std::enable_shared_from_this, но не знали о таком, поэтому сделали то же самое, но без перламутровых пуговиц»нет, я про то, что можно переписать код так, чтобы время жизни класса и его логика были разнесены по разным сущностям. А обратить внимание очевидно надо будет на те места, где в инстанс класса (в моем примере MyClassPrivate) передается shared_ptr на него же. И это намного проще, потому что привлечет внимание еще на этапе написания кода, а не во время отладки
И проверить будет сложнее, т.к. поиск по shared_from_this не покажет такого местаа вы какую задачу хотите решить — написания корректного кода, поиска багов или поиска мест где баги высоковероятны?
Надо ли разбирать методы устранения опасности — посмотрим по опросу через пару дней.
Судя по текущим комментам, треть аудитории не считает это опасностью вообще, треть считает материал слишком банальным и не заслуживающим их внимания, и ещё треть хочет изобрести умным указателям новые применения.
Ну и несколько исключительных читателей достаточно внимательны и опытны, чтобы самостоятельно решить, что с этим делать.
Предлагаю не обсуждать методы исправления — и так материал довольно объёмный, уже 60 комментов настрочили, а эта тема потянет ещё на целую статью такого же объёма.
Вот есть у класса функция типа
void CNet::SetInput(std::shared_ptr<CInput> cInput_Ptr)
{
cInput_Local_Ptr= cInput_Ptr
}
То есть, я просто указываю классу указатель на другой класс, который он запоминает для дальнейшего использования как входной параметр.
А вот дальше я не понимаю, как передать указатель, например, на такое (независимот от того, как был создан cData):
class CData
{
public:
CInput cInput;
};
CData cData;
cNet.SetInput(&cData.cInput); // вот как это сделать? Объект cInput не создавался динамически напрямую.
Если выставляете поля данных в public — это уже скорее struct, чем class.
я надеюсь, что между классами CInputClass и CInput есть связь типа «наследование»?
Опечатался. :) Это один и тот же класс. Исправил.
Простой путь — это завести в классе CData поле именно типа std::shared_ptr
Ну это-то очевидно. Но вот как без этого?
Если выставляете поля данных в public — это уже скорее struct, чем class.
Это для примера. Реально у меня классы — слои свёрточной нейросети и я хочу соединять входы и выходы (вход — указатель извне, выход — собственность класса слоя) и создавать классы обучения слоёв, порождаемые самими слоями (с передачей this в порождаемый класс обучения). И вот захотелось мне просто передать статический объект по указателю. А так как всё через shared_ptr сделано, то и возник вопрос, а как это сделать? Пока решение вижу одно — выкинуть все эти shared_ptr в параметрах функций типа SetInput нафиг и поставить и хранить обычный указатель. Тогда можно будет абсолютно любой указатель передавать, хоть умный, хоть нет и на что угодно указывающий. Только это, как я понимаю, сейчас совсем не приветствуется.
Сырые указатели легко превращаются в сырые указатели на уничтоженные объекты. Если у Вас есть достаточные способы контроля жизни объектов — то пожалуйста.
Ну и — я честно не понимаю, зачем плыть против течения.
Ну это рекомендация в стиле «тут так принято». Типа карго культа. А мне вот интересно, как именно в такой ситуации быть, если функция хочет shared_ptr, а объект статический. Перегружать deleter это как минимум некрасиво, так как извне неочевидны причины такой перегрузки.
Сырые указатели легко превращаются в сырые указатели на уничтоженные объекты.
Зависит от подхода и стиля. Если привык к «щас сбацаем и пусть умный указатель следит сам», то да, при переходе к обычным указателям начнутся утечки памяти из-за отсутствия опыта отслеживания всего созданного. А если привык сам следить за всем и не создавать динамические объекты без надобности, тогда нет. За 19 лет ни разу сырые указатели у меня не превратились в указатели на уничтоженные объекты. В конце-концов, не часто программе требуется постоянно что-то динамически создавать — обычно, при запуске создаются нужные объекты, а при завершении они уничтожаются.
Но, судя по всему, тут ещё не до полицейских разворотов — на второй передаче научиться бы ездить.
Решение с пустым deleter — первое, что пришло в голову, и наверняка есть ещё 2-3 варианта получше.
А может, вариантов получше и нет.
Но, судя по всему, тут ещё не до полицейских разворотов — на второй передаче научиться бы ездить.
Я вот не пойму, какой вам резон уже во втором сообщении пытаться меня уязвить по типу «мал ещё»? Да, я не часто использую эти умные указатели (у меня на основной рабочей системе QNX 6.3 просто вообще нет Си++ 11 компилятора. Вот нет и всё.) и гораздо меньше озабочен трюками с ними — меня волнует решение задачи пользователя и читаемость программы, а не нюансы извращений с умными указателями, пересыпанные костылями вроде enable_shared_from_this. И поэтому вполне логичен вышеобозначенный вопрос. Он элементарен для обычного указателя (да он даже не возникает в этом случае), но вызывает проблемы с умным.
Если по существу вопрса — лучше в личку.
Если по существу вопрса — лучше в личку.
Так а зачем? Я думаю, варианты решения много кому будут интересны.
Может кто знает ещё красивые методы решения.
Сырой указатель берётся из операции взятия адреса у какой-то штуковины, о времени жизни которой кто-то уже заботится.
Умный указатель по умолчанию тоже заботится о времени жизни той штуковины, которой его проинициализировали.
Итого: поведение по умолчанию приведёт к двойному удалению, что является UB.
Вы хотите это поведение изменить.
Для этого надо, чтобы кто-то один перестал заботиться о времени жизни этой штуковины.
В случае с std::shared_ptr этого можно добиться подсовыванием пустого deleter. Аналога std::unique_ptr::release() в std::shared_ptr нет и быть не может, а если бы и была — она должна была бы вызываться постфактум, т.е. примерно в деструкторе CNet. А вот пустой deleter можно подсунуть в том месте, где будете наводить связь.
Ну а как перестать заботиться о времени жизни штуковины, не живущей в умном указателе? Вероятно, просто воспользоваться new без последующего delete, т.к. со штуковиной, созданной на стеке, так не получится.
Первый вариант приводит к странной спорной конструкции, нуждающейся в пояснениях комментарием.
Второй вариант естественным образом приводит примерно к:
auto input = std::shared_ptr<CInput>(new CInput);
, и далее — прямо к улучшенной версии:
auto input = std::make_shared<CInput>();
Первый вариант — редкостное извращение, второй вариант — обычное штатное использование std::shared_ptr.
До тех пор, пока Вы контролируете хотя бы одну из сторон этой связи — просто нет нужды выдумывать вот это вот всё, и задачка является чисто спортивной.
А если Вы не контролируете обе стороны этой связи — то прошу объяснить, как так вышло.
Надо как-то согласовать сырой указатель с умным.
…
Итого: поведение по умолчанию приведёт к двойному удалению, что является UB.
Именно.
Ну а как перестать заботиться о времени жизни штуковины, не живущей в умном указателе? Вероятно, просто воспользоваться new без последующего delete, т.к. со штуковиной, созданной на стеке, так не получится.
Разве я не могу в тестовой функции создать все нужные классы на стеке, создать к ним shared_ptr без deleter'а и их уже передать в методы нужных классов? Методы отработают, функция завершится. Объекты разрушатся.
А если Вы не контролируете обе стороны этой связи — то прошу объяснить, как так вышло.
Тут дело просто в том, что не хочется всё создавать динамически. Поэтому я и спросил, какие есть вообще решения.
Разве я не могу в тестовой функции создать все нужные классы на стеке, создать к ним shared_ptr без deleter'а и их уже передать в методы нужных классов?
Можете. Но зачем бороться с RAII вместо того, чтобы его использовать? Ну я не знаю — это как вытащить из микроскопа оптику, чтобы он не сломался при забивании гвоздей.
Умные указатели совместимы с сырыми только в одну сторону. Если Вам одна и та же штуковина в одном месте нужна в умном указателе, а в другом — в сыром, то проще создать в умном и отдать туда и туда.
не хочется всё создавать динамически
Почему?
Если Вы так не любите/не умеете пользоваться умными указателями — зачем тогда:
void CNet::SetInput(std::shared_ptr<CInput> cInput_Ptr)
?
Почему?
Просто не нравится. :)
Если Вы так не любите/не умеете пользоваться умными указателями — зачем тогда:
Нет, я умными люблю пользоваться (конечно, они ведь удобны), но иногда хочется смешать, как в указанном примере. А функция принимает умный потому, что хочется в то же время в современном стиле Си++ всё-таки писать. А то опыта работы с ними и понимания их ограничений не будет. Без проблемы ведь не появится понимание метода решения. Да и вообще, бывает, придумаешь решение, а компилятор с ним работает не так, как задумывалось. Вот например. gcc 2.95 это компилирует, но работает неправильно — указатель приводится к базовому классу с вызовом функции без реализации (изначально — в примере там заглушка с выводом «CErrorsBased!»). Современный компилятор такое уже не компилирует вообще. А идея использования задумывалась интересной, но всё обломала реализация языка. :)
godbolt.org/z/uzTQzc
Виртуальное наследование в конечном классе совершенно излишне, ну и все ж вызов set_function_ptr() я бы через std::invoke переделал.
Виртуальное наследование в конечном классе совершенно излишне,
Тут дело в том, что базовый класс обеспечивает работу с разделяемой памятью, общей для всех классов, от него унаследованных. Я всё наследование сделал виртуальным в этой цепочке, чтобы нигде гарантированно не образовывалась копия этого базового класса и не приходилось за этим следить.
Интересно, почему ошибка возникает только при виртуальном наследовании в объединяющий класс.
я бы через std::invoke переделал.
Не могу. Это 17-й стандарт, а это фрагмент сделан по мотивам даже не 11-го (это было в моей QNX программе).
Достаточно, чтобы первый уровень наследования от общего класса был виртуальным.
Да, в данном случае каждый из объектов получит ссылку на этот базовый класс вместо его самого.
А почему вообще возникает эта ошибка — потому, что приведение к виртуальному базовому классу в compile time в общем случае невозможно из-за деталей реализации этого механизма.
Вот. Я нашёл в своё время статью «жуткие сведения об указателях на функции классов» со всем историческим геморроем при создании компиляторов с указателями на функции классов. Но тут какая штука? С точки зрения идеи я ведь ничего не нарушил.
но всё обломала реализация языка
Это признак того, что Вы сражаетесь со своими инструментами.
Просто не нравится
«Просто не нравиться» может цвет обоев. Или вкус еды. Или музыка. Или текстура ткани.
А в технических решениях принято взвешивать «за» и «против», и выбирать в соответствии с этим. Если решения при взвешивании оказываются эквивалентными — ну что ж, тогда дальше выбор за автором. Иногда конечно говорят «мне это решение не нравится», но обычно это означает что-то типа «чутьё подсказывает, что это приведёт к плохому коду, но вербализовать недостатки вот прямо на ходу не могу», или «я вижу, что недостатки перевешивают и могу это вербализовать, но это долго, а Вы вроде как и сами должны их видеть». Это совсем не то, что «настоящее» «не нравится».
Ходить вдоль края и заходить на тёмную сторону конечно обязательно надо. Но зачем начинать-то с этого? Вы же не начинали изучение сырых указателей с попыток разыменования указателей на удалённые объекты?
Касательно кода из примера — в личку. Слишком далеко ушли от темы статьи.
Это признак того, что Вы сражаетесь со своими инструментами.
Нет, это признак того, что не всё придуманное реализуемо средствами формальной системы языка. :)
чутьё подсказывает, что это приведёт к плохому коду, но вербализовать недостатки вот прямо на ходу не могу
Вот вы сами и ответили на этот вопрос. :)
Ходить вдоль края и заходить на тёмную сторону конечно обязательно надо. Но зачем начинать-то с этого? Вы же не начинали изучение сырых указателей с попыток разыменования указателей на удалённые объекты?
Вообще-то, делал и это. Знаете, как Windows98 крашилась если под MS-DOS в Watcom выделить память через new больше, чем есть в системе и сделать её очистку? Это было интересно. :)
Вообще-то, делал и это
И что, прям вот первой попыткой использования сырых указателей решили «а дай-ка я посмотрю, что будет»? Или может всё же пытались воспользоваться ими правильно, а ошибки иногда получались сами собой?
Вот как раз тот код, который у Вас уже возник, и является плохим. Не надо так делать.
Ну, код проекта тут я ещё не приводил — я привёл упрощённый тут же написанный пример. Сам же проект перестраивался раза 4, ибо придумать удобную архитектуру построения CNN и не запутаться в ней сходу не удалось.
И что, прям вот первой попыткой использования сырых указателей решили «а дай-ка я посмотрю, что будет»?
Я начинал с ZX-Spectrum с его ассемблером и бейсиком, а потому к моменту появления у меня IBM, я уже примерно представлял, что такое указатель и не погнушался побаловаться с ними и посмотреть, что будет, хотя, может, и не с самой первой попытки. :)
Зачем сначала брать умный указатель, а потом кастрировать его до состояния глупого, но только с повышенными накладными расходами? Если Вам не нужно поведение умного указателя — зачем Вы его используете?
А поведение — тадам — от сырого указателя.
Исходя из того, что внешние сущности успешно работают с возвращаемым кастратом std::shared_ptr — их устраивает именно поведение сырого указателя.
Вот его и надо возвращать, а не пытаться сделать «современно» — получается какое-то уродство.
Тут не то, чтобы желание — например, весь boost::asio на этом построен. И ничего. При аккуратном использовании в силу понимания происходящего он вполне себе торт.
Так что я даже не знаю, std::enable_shared_from_this ли виноват.
С таким же успехом можно было бы попытаться захватить невинно выглядящую ссылку, и почувствовать себя в среде immutable by default.
В коде нет никаких предпосылок думать, что _fn предназначен для какого-либо использования снаружи, тем более — после уничтожения родительского экземпляра SimpleCyclic (Вы же SimpleCyclic имеете в виду, говоря «в первом примере»?)
Более того, _fn в примере вообще никем и нигде не вызывается, даже внутри класса.
И написана эта конструкция именно так только по одной причине — это просто экстремально лаконичный способ организации циклической ссылки. И всё. Без высоких целей и идеологической подоплёки.
Или просто закрыть стрим, тогда async_read вызовет коллбэк с ошибкой и все остановится корректно.
Антипаттерн здесь вовсе не в shared_from_this, а использовании асинхронного чтения в runOnce. Тут надо или читать синхронно или дожидаться завершения и уж потом выходить.
Антипаттерн здесь вовсе не в shared_from_this, а использовании асинхронного чтения в runOnce
Эммм… Это как? Т.е. если мне поставлена задача написать асинхронную реализацию соединения — я должен ответить, что это — антипаттерн, и настаивать на том, чтобы отказаться от асинхронности в пользу блокирующих вызовов?
дожидаться завершения и уж потом выходить
Ага. Т.е. запущенный процесс ну никак нельзя прервать на уровне бизнес-логики. Просто прелесть.
Но почему Вы утверждаете, что этот способ — единственно верный?
Конструкция с Listener тоже не только способна выполнять роль асинхронного интерфейса, но и — о ужас! — иногда даже применяется в production в таком качестве.
Если Вы не заметили, статья не называется «Разбор вариантов построения асинхронных интерфейсов с выбором наиправильнейшего». И в начале статьи я предупредил, что демонстрирую в ней плохие техники. Это значит — показываю путь, следуя которому, можно нехило влипнуть, да ещё и не планирую обсуждать варианты выхода из ситуации ни в статье, ни в комментариях к ней. Хотите разбора — голосуйте «за» и ждите следующей статьи.
Так ещё Herb Sutter в прошлогоднем (вроде) докладе говорил: «Не использовать shared_ptr для кольцевой, только weak_ptr. Если захватить нечего, то и результат никому не нужен.» Даже с многопоточность такое работает в некоторых случаях.
Спасибо за отличный разбор проблемы!
'if (auto read = s.read())'
if statement
«declaration of a single non-array variable with a brace-or-equals initializer»
Сказ об опасном std::enable_shared_from_this, или антипаттерн «Зомби»