Я читаю статью <a href="https://habr.com/p/706866/" rel="nofollow">Умные указатели в современном C++ с точки зрения н...</a> и в разделе <b>Рациональное обоснование std::make_unique и std::make_shared</b> нашел следующий текст: <br/> <blockquote><br/> Во-вторых, это делает ваш код защищенным от исключений. Рассмотрим вызов функции, принимающей на вход два умных указателя, следующим образом:<br/> <pre><code class="cpp">void function(std::unique_ptr<A>(new A()), std::unique_ptr<B>(new B())) { ... }</code></pre><br/> Предположим, что new A() выполняется успешно, но new B() выбрасывает исключение: вы ловите его, чтобы возобновить нормальное выполнение программы. К сожалению, стандарт C++ не требует, чтобы объект A был уничтожен, а его память высвобождена: память тихо утекает, и нет способа ее очистить.<br/> </blockquote> <br/> У меня возникли несколько связанных вопросов: <br/> 1. В приведенном примере указано что это вызов функции, хотя написано как определение функции с типом void и фигурными скобками. Является ли это ошибкой оформления, или (что маловероятно) действительно возможно определение функции с использованием new в списке аргументов? <br/> 2. Если ответ на первый вопрос состоит в том, что это ошибка оформления и на самом деле это вызов функции, то почему в случае исключения объект A не удаляется? В конце концов, произойдет выход из текущей области видимости, и умный указатель должен удалить объект, освобождая память. Неужели я что-то пропустил, и по стандарту (в рамках C++11) допускается выполнение в порядке <code class="cpp">new A</code>, затем без создания временного объекта <code class="cpp">std::unique_ptr<A>(/*ук-ль на объект A*/)</code> снова выполнение <code class="cpp">new B</code>?
Ваши вопросы касаются важных аспектов работы с памятью и исключениями в C++. Давайте разберем их по порядку. 1. **Определение функции или вызов функции**: В приведенном коде: ```cpp void function(std::unique_ptr<A>(new A()), std::unique_ptr<B>(new B())) { ... } ``` действительно, это ошибка оформления. Это не является корректным синтаксисом для определения функции. На самом деле, в C++ не возможно передавать `new` напрямую в качестве аргумента функции. Правильный способ должен быть в использовании временных переменных для хранения умных указателей: ```cpp void function(std::unique_ptr<A> ptrA, std::unique_ptr<B> ptrB) { ... } function(std::make_unique<A>(), std::make_unique<B>()); ``` Таким образом, вы создаете временные умные указатели, которые будут корректно освободить выделяемую память в случае исключения. 2. **Почему объект A не удаляется при исключении**: Если бы вы попытались воспользоваться примером, приведенным в статье (если бы он был корректно оформлен), и `new A()` оказался успешным, а `new B()` выбрасывает исключение, то в реальности объект, созданный с помощью `new A()`, не будет автоматически управляться умным указателем, так как в вашем примере не было создания `std::unique_ptr`. Ваша попытка создать `std::unique_ptr` прямо в аргументах функции не приведет к созданию объектов, которые будут уничтожены, поскольку умные указатели не создаются в нужный момент. `new A()` вернет указатель на объект, но он не будет обернут в `std::unique_ptr` до тех пор, пока вы это не сделаете явно. Если вы используете `std::make_unique`, то исключение при выделении памяти для `new B()` приведет к тому, что автоматически созданный для `A` умный указатель освободит занятую память. Таким образом, наиболее корректный подход в C++ - это использовать `std::make_unique` и `std::make_shared` для создания умных указателей, так как это не только позволяет избежать утечек памяти, но и делает код более безопасным при обработке исключений.
Похоже на ошибку оформления. Это должен быть вызов функции. В тексте выше даже написано про вызов.
Тут автор считает, что сначала выполнится new A(), потом new B(), потом конструктор unique_ptr. Если исключение бросит B(), то действительно будет утечка памяти. Объект A, полученный через new умрет еще до оборачивания в unique_ptr. Такой сырой указатель автоматически не удалится.
Такая последовательность невозможна c С++17:
In a function call, value computations and side effects of the initialization of every parameter are indeterminately sequenced with respect to value computations and side effects of any other parameter.
Evaluations of A and B are indeterminately 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.
Но до C++17, действительно, компилятор может перемешать вычисления аргументов как угодно.