Вопрос звучал так: почему в этом коде течёт память. Может троллили и она не течёт, но когда я ответил, они радостно приняли ответ, показав что его и ждали и поехали дальше.
T() исключений не кидает, память внутри не выделяет.
f() память внутри не выделяет, возвращает int, но может кинуть исключение.
std::shared_ptr<T> t(new T( f() ) );
Я ответил такой бред: перед конструкированием объекта (вызовом T()) должна быть выделена память. Тогда возможна последовательность: malloc (от new), f(), T(). Если f() кинуло исключение, то выделенная память, в которой должен конструироваться объект, никем не освободится. Но это лютый бред, поскольку, вроде как если в ходе new летит исключение, то память, которую сам оператор new выделил, гарантированно освободится обратно. Да, исключение полетело не из конструктора T(), а при вычислении аргументов для передачи в конструктор, но я всегда думал что в случае оператора new это всё то же самое, как если бы полетело из конструктора.
Короче, я стал сомневаться в том, что собеседующий был вменяем. Течёт скорее всего у него где-то в другом месте.
If any part of the object initialization described above terminates by throwing an exception, storage has been obtained for the object, and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new-expression
Цитата оборвалась на самом интересном месте, дальше же прямо ответ на вопрос идёт:
... If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object’s memory to be freed. [ Note: This is appropriate when the called allocation function does not allocate memory; otherwise, it is likely to result in a memory leak. — end note ]
Помнится, ранее речь шла не о таких случаях как в стартовом сообщении, а о выражениях вида:
void demo(unique_ptr<T> a, shared_ptr<X> b){...}
...
demo(unique_ptr<T>( new T(f()) ), shared_ptr<X>( new X(f(), g(), h()) ));
И фокус состоял в том, что компилятор не обязательно должен вызвать конструктор unique_ptr сразу после того, как выполнит new T(). Поскольку порядок вычисления аргументов не определен, то компилятор может вызвать new T, затем new X, только затем конструкторы unique_ptr и shared_ptr. Поэтому если после new T возникнет исключение в конструкторе X, то произойдет утечка памяти.
А вот что именно интервьюеры нашли в примере из стартового сообщения — это интересно.
не будет тут утечки. Если только не убрать деалокатор умышленно.
будет если 2 таких параметра будет передаваться. Только порядок вызова функций определит конпелятор и может вызвать сначала все new, а потом уже конструкторы умных указателей. Есть подозрение, что интревьюйеры просто урезали пример с 2 параметрами и выё..ваются.
По идее, происходит такая последовательность вызовов:
Выделяется память
Вычисляются аргументы конструктора (порядок не определён, но точно до вызова конструктора)
Вызывается конструктор
Если конструктор кидает исключение, то память освободится. А вот считается ли вычисление аргументов, частью вызова конструктора? Физически на всех распостраннёных системах это точно не так. А что говорит по этому поводу стандарт - я не знаю.
при вызове new должен уже быть вычислен его аргумент. То есть если он (f()) кинет исключение, то память не будет освобождена, потому что не будет выделена.
далее исключение может кинуть оператор копирования того, что возвращает f( ), но и в этом случае память будет освобождена.
потом может кинуть исключение конструктор. собссно тут все понятно.
Более того, не определенно что случится раньше вызов f() или выделение памяти. Но они оба случаться точно раньше вызова конструктора. Ну это как бы и логично и в духе плюсов :)
Guideline: Use make_shared (or, if you need a custom allocator, allocate_shared) to create an object you know will be owned by shared_ptrs, unless you need a custom deleter or are adopting a raw pointer from elsewhere.
видимо, моя теория о том, что около 80% биологических организмов, получающих деньги за код на крестах, пишут его не приходя в сознание, верна. вопрос разбирается в 2 этапа:
1. надо понимать разницу между выражением new и оператором new:
A new-expression may obtain storage for the object by calling an allocation function (3.7.4.1). If the
new-expression terminates by throwing an exception, it may release storage by calling a deallocation function
(3.7.4.2).
в выражении new T( f() ) утечки нет.
2. как работает конструктор shared_ptr. в частности то, что он вызывает ещё одно выделение памяти:
6 Throws: bad_alloc, or an implementation-defined exception when a resource other than memory
could not be obtained.
7 Exception safety: If an exception is thrown, delete p is called.
какое отношение порядок вычисления имеет к осбенностям поведения выражения new или конструктора shared_ptr?
это?
12) The call to the allocation function (operator new) is indeterminately sequenced with respect to (until C++17)sequenced before (since C++17) the evaluation of the constructor arguments in a new-expression
здесь написано «sequenced before (since C++17)», это значит сначала выделить память, а потом вызвать f(). ещё написано «indeterminately sequenced ... (until C++17)», т.е. вообще как попало.