[assign][ptr_container] Exception safety of ptr_list_of, ptr_push_back, ptr_map_insert, etc.
I am trying to understand how ptr_list_of or ptr_push_back provide more exception safety. I can write something like: boost::ptr_vector<Foo> vec; push_back(vec)(new Foo)(new Foo); Is the order of evaluation of the two "new Foo" expressions indeterminate here? If not, then if the second new throws, the destructor of vec should be invoked. The Foo object allocated by the first new and now part of vec should also get released as part of that. -- Aaron Levy aaron.levy@yandex.com
On 24-09-2014 04:45, Aaron Levy wrote:
I am trying to understand how ptr_list_of or ptr_push_back provide more exception safety.
I can write something like:
boost::ptr_vector<Foo> vec; push_back(vec)(new Foo)(new Foo);
Is the order of evaluation of the two "new Foo" expressions indeterminate here? If not, then if the second new throws, the destructor of vec should be invoked. The Foo object allocated by the first new and now part of vec should also get released as part of that.
That would not be safe. The intention is that you can forward to the constructor such that the new expression happens inside the library: push_back( vec )()(); See http://www.boost.org/doc/libs/1_56_0_b1/libs/assign/doc/index.html#ptr_push_... kind regards -Thorsten
I am trying to understand how ptr_list_of or ptr_push_back provide more exception safety.
I can write something like:
boost::ptr_vector<Foo> vec; push_back(vec)(new Foo)(new Foo);
Is the order of evaluation of the two "new Foo" expressions indeterminate here? If not, then if the second new throws, the destructor of vec should be invoked. The Foo object allocated by the first new and now part of vec should also get released as part of that.
That would not be safe.
The intention is that you can forward to the constructor such that the new expression happens inside the library:
push_back( vec )()();
See
http://www.boost.org/doc/libs/1_56_0_b1/libs/assign/doc/index.html#ptr_push_...
Yes I saw that the documentation says these are exception safe and understandably the arguments are forwarded to the constructors. I was just trying to understand the scenarios under which code like the example below cause exception-safety issues. boost::ptr_vector<Foo> vec; push_back(vec)(new Foo)(new Foo); My reasoning was that the nth "new Foo" gets evaluated after the n-1 "new Foo" expressions which by then are already part of the container. So this is possibly no less exception-safe. But what is an example of a scenario where they are exception unsafe? Is it when the constructor takes arguments and some arguments are copied and one of the copy operations throws, or something like that? Thanks.
On 24/09/2014 21:52, Aaron Levy wrote:
Yes I saw that the documentation says these are exception safe and understandably the arguments are forwarded to the constructors. I was just trying to understand the scenarios under which code like the example below cause exception-safety issues.
boost::ptr_vector<Foo> vec; push_back(vec)(new Foo)(new Foo);
My reasoning was that the nth "new Foo" gets evaluated after the n-1 "new Foo" expressions which by then are already part of the container.
That is not correct. The compiler is free to evaluate the independent subexpressions in any order; it is only constrained by dependent expressions. For example, the below code is a valid rewrite of the above by the compiler internals: boost::ptr_vector<Foo> vec; Foo *tmp2 = new Foo; auto tmp3 = push_back(vec); Foo *tmp1 = new Foo; auto tmp4 = tmp3(tmp1); tmp4(tmp2); In particular note that if the second "new Foo" (to tmp1) throws -- which is possible due to out-of-memory even if for no other reason -- then the pointer in tmp2 will be leaked, as it hasn't yet been assigned to the container. The same can occur if push_back or if tmp3() throw. The compiler is free to allocate tmp1 and tmp2 in any order; the only constraints are: 1. It must call their constructors after the constructor of vec is called. 2. It must create tmp1 before it can call tmp3 to create tmp4. 3. It must create tmp2 before it can call tmp4. Similarly if there were any parameters being passed to the Foo constructor (whether using this method or using constructor forwarding), temporary values for these could be created in any order -- not just out of parameter order but also not in the order of constructing the objects they're being passed to. It's the same principle as this: f(a(), b(), c(), d()); In the above, the compiler can call a,b,c,d in any order. The only constraint is that it must call all four of them before it can call f.
participants (3)
-
Aaron Levy
-
Gavin Lambert
-
Thorsten Ottosen