Using an object without a copy and without the move constructor without moving in the vector. What actually breaks and how can I confirm this?

I checked a lot of constructor / vector / movements without extra threads, but I'm still not sure what actually happens when something should go wrong. I cannot make a mistake when I expect, so either my little test is wrong or my understanding of the problem is wrong.

I use the BufferTrio object vector, which defines the noexcept (false) move constructor and removes every other constructor / assignment statement, so there is nothing to return to:

BufferTrio(const BufferTrio&) = delete; BufferTrio& operator=(const BufferTrio&) = delete; BufferTrio& operator=(BufferTrio&& other) = delete; BufferTrio(BufferTrio&& other) noexcept(false) : vaoID(other.vaoID) , vboID(other.vboID) , eboID(other.eboID) { other.vaoID = 0; other.vboID = 0; other.eboID = 0; } 

Things are compiled and executed, but from https://xinhuang.imtqy.com/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html :

std :: vector will use relocation when it needs to increase (or decrease) capacity if the relocation operation is no exception.

Or from Optimized C ++: Proven Performance Improvement Techniques by Kurt Gunterot:

If the move constructor and move destination operator are not declared noexcept, std :: vector uses less efficient copy operations.

Since I deleted them, I understand that something must be broken here. But with this vector everything works fine. Therefore, I also created a base loop that push_backs half a million times a dummy vector, and then replaced this vector with another singleton dummy vector. For example:

  vector<BufferTrio> thing; int n = 500000; while (n--) { thing.push_back(BufferTrio()); } vector<BufferTrio> thing2; thing2.push_back(BufferTrio()); thing.swap(thing2); cout << "Sizes are " << thing.size() << " and " << thing2.size() << endl; cout << "Capacities are " << thing.capacity() << " and " << thing2.capacity() << endl; 

Output:

 Sizes are 1 and 500000 Capacities are 1 and 699913 

Still no problem, therefore:

Should I see something wrong, and if so, how can I demonstrate it?

+7
c ++ constructor vector c ++ 11 move
source share
3 answers

Vector redistribution tries to offer a guarantee of exclusion, that is, an attempt to maintain the original state if an exception is selected during the redistribution operation. There are three scenarios:

  • Element type nothrow_move_constructible : Redistribution can move elements that will not throw exceptions. This is an effective case.

  • Element type CopyInsertable : if the type does not match nothrow_move_constructible , this is enough to provide a reliable guarantee, although copies are executed during redistribution. This was the old default behavior in C ++ 03 and was less effectively reduced.

  • The item type is not CopyInsertable and nothrow_move_constructible . As long as it can still be constructive, as in your example, vector redistribution is carried over, but does not provide any guarantees of exclusion (for example, you can lose elements if the throw is built).

A normative wording that states that this applies to various redistribution functions. For example, [vector.modifier] / push_back says:

If an exception is thrown to insert one element at the end and T is CopyInsertable or is_nothrow_move_constructible_v<T> is true , there are no effects. Otherwise, if an exception is CopyInsertable non- CopyInsertable T move constructor, the effects are undefined.

I don’t know what the authors of the posts you cite had in mind, although I can imagine that they mean that you want a strong guarantee of exclusion, and therefore they would like to refer you to cases (1) or (2).

+9
source share

Nothing bad happens in your example. From std::vector::push_back :

If the T move constructor is not noexcept , and T is not CopyInsertable in *this , the vector will use the throwing move constructor. If it throws, the warranty is void, and the effects are not specified.

std::vector prefers not to throw move constructors, and if there are none, it returns to the copy constructor (throws or not). But if this is also not available, then he should use the throwing constructor. In principle, the vector tries to save you from throwing constructors and leaving objects in an undefined state.

So, in this regard, your example is correct, but if your motion constructor really threw an exception, then you will have unspecified behavior.

+3
source share

TL; DR As long as the MoveInsertable type, you're fine, and you're fine here.

A type of type MoveInsertable, if the specified dispatch container A , the dispenser instance assigned to the variable m , a pointer to T* called p , and the r-value of type T following expression is well formed:

 allocator_traits<A>::construct(m, p, rv); 

For your std::vector<BufferTrio> you use std::allocator<BufferTrio> by default, so this construct call calls

 m.construct(p, std::forward<U>(rv)) 

Where U is the forwarding reference type (in your case, it is BufferTrio&& , rvalue reference)

So far so good

m.construct will use place-new to create an element in place ([allocator.members])

 ::new((void *)p) U(std::forward<Args>(args)...) 

In no case does this require noexcept . This is only for reasons of guarantee exclusion.

[vector.modifiers] indicates that for void push_back(T&& x);

If an exception is thrown by a non- CopyInsertable T constructor movement, no effects are defined.


Finally, Regarding your swap ( Emphasis mine ):

[container.requirements.general]

The expression a.swap(b) , for containers A and b standard container type other than array , must exchange the values ​​of A and b without invoking the move, copy or swap operations in a separate container Elements .

+3
source share

All Articles