Страницы

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

воскресенье, 5 января 2020 г.

Порядок конструирования аргументов функции

#cpp


Встретил статью «Пять подводных камней при использовании shared_ptr»,
в которой утверждается, что:


  в строке кода:

foo( shared_ptr(new Widget), bar() );

  
  порядок конструирования не определен.
  Все зависит от конкретного компилятора
  и флагов компиляции. Например, это
  может произойти так:
  
  
  new Widget 
  вызов функции bar 
  конструирование shared_ptr
  вызов функции foo
  


Почему так? Я считал, что в списке параметров функции точки следования определены
запятыми и аргументы (границы между которыми очерчивают запятые) действительно могут
быть обработаны в любом порядке, НО каждый отдельный аргумент, если уж начал обрабатываться
программой, то обрабатывается до конца. А т.к. shared_ptr(new Widget) - это
отдельный аргумент, то он обработается целиком (т.е. в приведённом списке пункты 1
и 3 всегда будут идти подряд).

Разве не так? Ткните, пожалуйста, меня носом в "что бы прочесть", чтобы уяснить свою
ошибку.
Спасибо.

Обновление

@KoVadim, в статье рассматривается случай, когда выполнение bar() бросает исключение.
Тогда (исходя из предложенного порядка обработки аргументов) получается, что объект
Widget сконструирован, но shared_ptr ещё нет (следовательно и не завладел объектом
Widget). Отсюда утечка памяти.

Обновление 2

@KoVadim, да, действительно, Вы правы. Дело именно в этом. Прочёл дальше комментарии
к статье - там кто-то уже задал подобный вопрос. В ответ ему посоветовали ссылку http://www.gotw.ca/gotw/056.htm
Тут разбирается подобный случай. А причина неопределённости, как Вы уже заметили, в
том, что запятая, отделяющая параметры функции друг от друга - это разделитель (comma
separator), а не оператор запятая (comma operator). А точкой следования из них является
только оператор.
Спасибо за пинок в правильном направлении.
    


Ответы

Ответ 1



В статье на хабре написано все верно. Вот выдержка из cpp reference: “Sequenced-before” is an asymmetric, transitive, pair-wise relationship between evaluations within the same thread (it may extend across threads if atomic types and memory barriers are involved). If a sequence point is present between the subexpressions E1 and E2, then both value computation and side effects of E1 are sequenced before every value computation and side effect of E2 If evaluation A is sequenced before evaluation B, then evaluation of A will be complete before evaluation of B begins. (since C11) If A is not sequenced before B and B is sequenced before A, then evaluation of B will be complete before evaluation of A begins. (since C11) If A is not sequenced before B and B is not sequenced before A, then two possibilities exist: (since C11) evaluations of A and B are unsequenced: they may be performed in any order and may overlap (within a single thread of execution, the compiler may interleave the CPU instructions that comprise A and B) evaluations of A and B are indeterminably-sequenced: they may be performed in any order but may not overlap: either A will be complete before B, or B will be complete before A. The order may be the opposite the next time the same expression is evaluated. Как видите, в случае, если порядок А и Б не определен, возможны два варианта: с оверлапом и без него. Собственно, написанная Вами выше последовательность, когда bar() вызывается между new и вызовом конструктора, является первым случаем, вполне возможным согласно стандарту. Место, о котором я говорю: evaluations of A and B are unsequenced: they may be performed in any order and may overlap (within a single thread of execution, the compiler may interleave the CPU instructions that comprise A and B)

Ответ 2



Запятые, разделяющие аргументы функции, и оператор запятая - это разные вещи. По поводу порядка вычисления. А какая разница? Проблемы будут только в том случае, если конструктор shared_ptr зависит от bar() (глобальными переменными), но это уже страх и ужас.

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

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