TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #826 4.87K
Одно значимое улучшение С++17
#опытным

У компилятора большая свобода в том, что и как он может делать с исходным кодом при компиляции.

Возьмем, например, вызов функции:

f( g(expr1, expr2), h(expr3) );


В каком порядке вызываются expr1, expr2, expr3, g, h и f?

Культурно западный человек интуитивно будет представлять обход в глубину слева направо. То есть порядок вычисления будет примерно такой: expr1 -> expr2 -> g -> expr3 -> h -> f.

Однако это абсолютно не совпадает с тем как поступает компилятор в соответствии со стандартом.

Что было до С++17?

Было единственное правило: все аргументы функции должны быть вычислены до вызова функции. Все!

То есть могло теоретически мог бы быть такой порядок: expr2 -> expr3 -> h -> expr1 -> g -> f.

Полный бардак! И это приводило на самом деле к неприятным последствиям.

Что если мы принимаем в функцию два умных указателя и попробуем вызвать ее так:

void bar(std::unique_ptr<SomeClass1> a, std::unique_ptr<SomeClass2> b) {}

bar(std::unique_ptr<SomeClass1>(new SomeClass1{}), std::unique_ptr<SomeClass1>(new SomeClass2{}));


Какие тут могут быть проблемы?

Итоговый порядок вычислений может быть следующий:

new SomeClass1{} -> new SomeClass2{} -> std::unique_ptr<SomeClass1> -> std::unique_ptr<SomeClass2>


Что произойдет, если SomeClass2 выкинет исключение? Правильно, утечка памяти. Для объекта, созданного как new SomeClass1{}, не вызовется деструктор.

Эту проблему решали с помощью std::make_* фабрик умных указаателей:

bar(std::make_unique<SomeClass1>(), std::make_unique<SomeClass2>());


Нет сырого вызова new, а значит если из второго конструктора вылетит исключение, то первый объект будет уже обернут в unique_ptr и для него вызовется деструктор.

Это было одной из мощных мотиваций использования std::make_* функций для умных указателей.

Что стало с наступлением С++17?

f(e(), g(expr1, expr2), h(expr3));


До сих пор неопределено в каком порядке вычислятся e, f и h. Или expr1 и expr2.

Но четко прописано, что если компилятор выбрал вычислять expr1 первым, то он обязан полностью вычислить g прежде чем перейти у другим аргументам. Это уже примерно как обход в глубину, только порядок захода в ветки неопределен.

Теперь такой код не будет проблемой:

void bar(std::unique_ptr<SomeClass1> a, std::unique_ptr<SomeClass2> b) {}

bar(std::unique_ptr<SomeClass1>(new SomeClass1{}), std::unique_ptr<SomeClass1>(new SomeClass2{}));


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

Это немного обесценило использование std::make_* функций. Но их все равно предпочтительно использовать из-за отсутствия явного использования сырых указателей.

Fix problems. Stay cool.

#cppcore #memory #cpp17
  • 👍 38
  • ❤ 15
  • 🔥 10
  • ❤‍🔥 4
More from @grokaemcpp
  1. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  2. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  3. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  4. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  5. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  6. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →