Как стать автором
Обновить

Method chaining

Время на прочтение2 мин
Количество просмотров24K
Всего голосов 49: ↑35 и ↓14+21
Комментарии37

Комментарии 37

Никогда не понимал, в чём проблема городить поменьше побочных эффектов.

Ограничения на порядок вычисления аргументов уменьшают свободу манёвра компилятора в раскладывании промежуточных значений по регистрам. Такое решение не особо в духе языка, который позиционирует себя как не вносящий потерь производительности без надобности.
Никогда не понимал, в чём проблема городить поменьше побочных эффектов.
— Лично мне краткий код с побочными эффектами понять проще: не приходится бегать взглядам по строчкам. Но это ИХМО.
не приходится бегать взглядам по строчкам

Опровергается последним сниппетом из оп-поста: чтобы понять, что именно придет на вход print_result, придется побегать взглядом туда-сюда.
Оптимизация же.
Совершенно верно, почти все что в с++ неопределенно, или ведет к неопределенному поведению — все для оптимизации. Например, не инициализированные переменные.
Способ забавный конечно, но не особо он сокращает код.

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

итак для примера из поста:
мы экономим w. \n для каждого вызова метода в замен Worcker&… return this;\n для каждого описания.
(\n — символ переноса строки)
значит нужно где то 5-6 хитро-экономичных вызовов чтобы оправдать излишний код.

2. Второе не менее важное — это быстродействие, любое действие в программе стоит машинного времени, return this не исключение, а так как this значение неоднозначное, то оптимизация вам тут не поможет. Так что не дай бог использовать такой вот код в критических секциях.
1. Важен не размер описания, а размер вызывающего кода. Вспомните boost — не дай бог случайно залезть в описание, скажем, boost::variadic (ещё до С++11), но зато в вызывающем коде всё очень лаконично.
2. return *this просто возвращает ссылку на текущий объект. Ничего не копируется, накладных расходов минимум, конструкция используется очень часто, хотя бы в std::iostream.
Дело в том, что хотя стандарт и гарантирует, что process() будет вызвана перед print_result(), но не гарантируется, что перед аргумент функции print_result будет вычислен после выполнения process().

Вы ошибаетесь.

Стандарт гарантирует следующее:
When calling a function (whether or not the function is inline), there is a sequence point after the evaluation
of all function arguments
(if any) which takes place before execution of any expressions or statements in
the function body. There is also a sequence point after the copying of a returned value and before the execution
of any expressions
outside the function11)

Таким образом аргументы второй функции не могут быть вычислены до аргументов первой, т.к. они разделены двумя точками следования (sequence point).
Нет вы. Точек следования уже три года как нет. Аргументы функций в полном выражении (читай: до точки с запятой) могут вычисляться вообще как угодно, лишь бы они были вычислены до входа в функцию, которой они нужны.
В C++11 формулировка другая:
[intro.execution] Par 15:
When calling a function (whether or not the function is inline), every value computation and side effect
associated with any argument expression, or with the postfix expression designating the called function, is
sequenced before execution of every expression or statement in the body of the called function. [ Note: Value
computations and side effects associated with different argument expressions are unsequenced. — end note ]
Every evaluation in the calling function (including other function calls) that is not otherwise specifically
sequenced before or after the execution of the body of the called function is indeterminately sequenced with
respect to the execution of the called function.

То есть гарантируется только, что аргументы функции будут вычислены до вызова функции, а порядок вычисления других выражений по отношению к вызову функции не определён.
Формулировка другая, но в применении к данному вопросу она дает то же результат.
То что раньше называлось точками следования (sequence point), теперь называется упорядочиванием (sequencing). Что по сути является тем же самым, только позволяет еще описывать поведение неупорядоченного (unsequenced) кода.
Т.е. когда мы говорим что два выражения упорядочены это то же самое что в старом стандарте что между выражениями есть точка следования.

Таким образом интрепретация нового текста стандарта в старых терминах такая:
every value computation and side effect
associated with any argument expression, or with the postfix expression designating the called function, is
sequenced
before execution of every expression or statement in the body of the called function

Это означает существует точка следования между вычислением всех аргументов функции и ее непосредственно выполнением.

Таким образом как старом так и в новом стандарте, вычисление аргументов для двух последовательно идущих в коде функций производится раздельно (если сами вызовы между собой упорядочены, как в данном случае).
Там написано ровно то, что написано: «все вычисления и побочные эффекты, связанные с вычислением аргументов вызываемой функции или самой вызываемой функции, выполняются перед вычислением тела вызываемой функции». Здесь нет ничего о том, что находится вне тела функции.
Точки следование и упорядочивание — это не одно и то же. Точка следования — это такой барьер для побочных эффектов: все побочные эффекты должны быть применены и видимы после точки следования для всех остальных выражений. Упорядочивание касается только выражений, которые упорядочены относительно друг друга. Плюс, для упорядочивания есть три понятия: определённо упорядочены, неопределённо упорядочены и не упорядочены. Всё, что явно не прописано, считается не упорядоченным.
Задумался.
Пойду перечитаю стандарт :)
Энивей, что-то меня понесло. Даже в C++03 аргументы могут быть вычислены до вызова всех функций полного выражения.

Точки следования касаются побочных эффектов. То есть они гарантируют, что внутри тела print_result data == 185. И упорядочивание тоже это гарантирует (вызовы функций внутри одного полного выражения не пересекаются, выражение слева после точки вычисляется перед выражением справа).

Но порядка вычисления аргументов нескольких функций это не касается. data + 2 может вычисляться как при data == 0 (сначала вычисляются все аргументы, потом вызывается process, потом print_resutl), так и при data == 185 (сначала аргументы process, потом вызов process, потом аргументы print_result, потом print_result).
Статья заканчивается невероятно неожиданно. Опасные ситуации наверняка есть и много, если раскрутить ситуации, касающиеся созданий, удалений объектов, исключений и прочих классических недопонимаемых ситуаций. Ещё предположу, что с константными методами будут неудобства, так как после первого же константного в цепочку соберутся только константные.
А вообще способ странноватый, и совсем не понимаю, за что его любят в java.
если все сделано добротно, можно делать вот такие штуки:
report->loadFromFile('./template')->trim()->addDaysCounters()->addCurrentDate()->createPDF()->sendToClientViaEmail()->sendAdminNotice()->die()

НЛО прилетело и опубликовало эту надпись здесь
Этим паттерном обычно компенсируют отсутствие именованных параметров в с++
Самый хороший пример из мира плюсов — потоковые операторы из stl. Как method chaining, только функциональнее.
Тем временем, этот паттерн (это называется fluent interface) очень популярен в .NET и работает там корректно. Чтобы не был определен порядок вызовов — это же как сильно напортачить надо? Ужас. Вот из-за таких вещей портится впечатление от плюсов в целом.
Порядок вызовов определён. Неопределён лишь порядок вычисления их аргументов. Что, как показано, тоже довольно неприятно. При желании можно провести аналогию с функциональными языками.
Достаточно было упомянуть, что этот паттерн называется Fluent interface (удивительно что автор топика этого знает).
Паттерн конечно интересный, но применять его стоит аккуратно. В большинстве случаев эта цепочка не рандомная, и в ней нельзя поменять местами вызовы методов. Как пользователь класса я не хочу знать о том, в каком порядке мне их звать, я хочу иметь один метод, к примеру doWork(), который сделает меня счастливым.
Его можно использовать только в том случае, когда методы на самом деле независимы (или почти независимы) и их можно комбинировать в любом порядке. Например, всякого рода парсеры, сериализаторы, etc.
Тем не менее, этот паттерн очень даже прекрасно вписывается в стандартный контейнер std::string
Я видел места где это может быть полезно — декларативная инициализация объекта. Декларативная разметка не подразумевает зависимости от порядка, и каждый аспект не зависит от другого:
view->setSize(10, 10)->setAlign(Stretch)->setSnap(true)
и т.п.
При использовании в других целях, вылезают проблемы, которые описаны в статье и в комментариях выше.
Декларативную инициализацию, как и любую другую инициализацию, лучше не выносить за пределы вызова конструктора. Бонусов от этого масса, в том числе возможность сделать объект immutable. А при инициализации через конструктор, даже самой изощрённой, описанное в топике мало пригодится. Ну и самая красивая декларативность в плюсах — она на шаблонах и простых объектах.
Верно. Но я показал пример а-ля виджет. Он не может быть неизменяемый. Его конструктор лопнет от всех инициализаций. Все его свойства и так пишутся отдельными методами, вот в этом случае этот патерн очень даже подходит.
А при инициализации через конструктор, даже самой изощрённой, описанное в топике мало пригодится.

Спрятать страшный конструктор с десятью аргументами, сделать билдер. А в билдере разбираемый здесь чейнинг методов будет смотреться просто идеально.
GUI -библиотека Ultimate++ использует этот паттерн
Стандартная библиотека тоже.
Буквально пару дней назад отловил баг с этим паттерном:

    worker const & w = worker()
        .set_data(data_t{})
        .process()
        .send_result()
        .print_log();


Догадайтесь, где баг. Можно сказать, что автор (не я) сам себе злобный буратино. Однако, осадочек, как говорится, остался.
worker умирает раньше, чем надо? битая ссылка получается.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории