Account Options

  1. Sign in
The old Google Groups will be going away soon, but your browser is incompatible with the new version.
Google Groups Home
« Groups Home
Move, std::vector and gcc: which is this correct?
There are currently too many topics in this group that display first. To make this topic appear first, remove this option from another topic.
There was an error processing your request. Please try again.
flag
  3 messages - Collapse all  -  Translate all to Translated (View all originals)
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Edward Rosten  
View profile  
 More options Oct 17 2012, 6:30 pm
Newsgroups: comp.lang.c++.moderated
From: Edward Rosten <firstname.dot.lastn...@googlemail.com>
Date: Wed, 17 Oct 2012 17:25:04 CST
Local: Wed, Oct 17 2012 7:25 pm
Subject: Move, std::vector and gcc: which is this correct?
I have a simple program below which defines or defaults various
combinations of move and copy constructors and puts the classes into a
std::vector.

#include <iostream>
#include <vector>
using namespace std;

struct Both
{
        Both() = default;

        Both(Both&&)
        {
                cout << "Move\n";
        }

        Both(const Both&)
        {
                cout << "Copy\n";
        }

};

struct MoveOnly
{
        MoveOnly() = default;
        MoveOnly(MoveOnly&&)
        {
                cout << "Move\n";
        }

};

struct DefaultMove
{
        DefaultMove() = default;
        DefaultMove(DefaultMove&&)=default;
        DefaultMove(const DefaultMove&)
        {
                cout << "Copy\n";
        }

};

template<class C> void test()
{
        vector<C> f;
        for(int i=0; i < 5; i++)
        {
                cout << "Iteration " << i;
                if(f.capacity() <= i)
                        cout <<  " resize expected";
                cout << endl;

                f.push_back({});
                cout << endl;
        }

};

int main()
{
        cout << "------------------------Both:\n";
        test<Both>();

        cout << "-----------------------MoveOnly:\n";
        test<MoveOnly>();

        cout << "-----------------------DefaultMove:\n";
        test<DefaultMove>();

}

With gcc 4.6, I get the following output:

------------------------Both:
Iteration 0 resize expected
Move

Iteration 1 resize expected
Move
Move

Iteration 2 resize expected
Move
Move
Move

Iteration 3
Move

Iteration 4 resize expected
Move
Move
Move
Move
Move

-----------------------MoveOnly:
Iteration 0 resize expected
Move

Iteration 1 resize expected
Move
Move

Iteration 2 resize expected
Move
Move
Move

Iteration 3
Move

Iteration 4 resize expected
Move
Move
Move
Move
Move

-----------------------DefaultMove:
Iteration 0 resize expected

Iteration 1 resize expected

Iteration 2 resize expected

Iteration 3

Iteration 4 resize expected

This is what I expected. When the vector resizes itself, the move
constructor is always called. In the final case, this is implied by the
copy constructor not being called.

However, with 4.7, I get:

------------------------Both:
Iteration 0 resize expected
Move

Iteration 1 resize expected
Move
Copy

Iteration 2 resize expected
Move
Copy
Copy

Iteration 3
Move

Iteration 4 resize expected
Move
Copy
Copy
Copy
Copy

-----------------------MoveOnly:
Iteration 0 resize expected
Move

Iteration 1 resize expected
Move
Move

Iteration 2 resize expected
Move
Move
Move

Iteration 3
Move

Iteration 4 resize expected
Move
Move
Move
Move
Move

-----------------------DefaultMove:
Iteration 0 resize expected

Iteration 1 resize expected

Iteration 2 resize expected

Iteration 3

Iteration 4 resize expected

In other words, when both copy and move are defined explicitly, the copy
constructor gets called but if the move constructor is defaulted, then it
gets called in preference to the copy constructor.

I would have expected the GCC 4.6 behaviour to be correct. Am I mistaken?

-Ed

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Daniel Krügler  
View profile  
 More options Oct 18 2012, 9:17 am
Newsgroups: comp.lang.c++.moderated
From: Daniel Krügler <daniel.krueg...@googlemail.com>
Date: Thu, 18 Oct 2012 06:16:45 -0700 (PDT)
Local: Thurs, Oct 18 2012 9:16 am
Subject: Re: Move, std::vector and gcc: which is this correct?
On 2012-10-18 01:25, Edward Rosten wrote:

> I have a simple program below which defines or defaults various
> combinations of move and copy constructors and puts the classes into a
> std::vector.

[snip]

> This is what I expected. When the vector resizes itself, the move
> constructor is always called. In the final case, this is implied by the
> copy constructor not being called.

[snip]

> In other words, when both copy and move are defined explicitly, the copy
> constructor gets called but if the move constructor is defaulted, then it
> gets called in preference to the copy constructor.

> I would have expected the GCC 4.6 behaviour to be correct. Am I mistaken?

You are mistaken. Let me explain a bit the reasoning of my assertion:

Originally move construction was considered as a "must-be-nothrow"
operation in the library, but during the standardization process several
good reasons were brought on the table that argued in favour for
allowing potentially throwing move-operations. Potentially throwing
move-operations were the main reason why noexcept was invented as both
deduction mechanism and exception-specifier. A fundamental difference of
move operations versus copy operations is that the former can
irreversibly change the state of the *source* of the move. This would
mean that a throwing move prevents that an operation can hold the strong
exception guarantee, that is, either the operation has succeeded or
there are no effects.

If you look at the specification of vector's push_back (and some other
insertion operations) you find the following specification:

"If an exception is thrown other than by the copy constructor, move
constructor, assignment operator, or move assignment operator
of T or by any InputIterator operation there are no effects. If an
exception is thrown by the move constructor of a non-CopyInsertable T,
the effects are unspecified."

So for every type that has a copy constructor (using the
std::allocator), the standard requires that vector's push_back either
succeeds or has no effects. If your type has a potentially throwing move
constructor (This is so in your example), it has to fall-back to a copy
operation when it can. If there is no copy possible, there is no more
the guarantee that this operation has the strong exception guarantee.
This explains why gcc 4.7 correctly falls back to copy operations for
type Both (The first move can be done safely, because it does not affect
the existing container elements), because it has a potentially throwing
move constructor and an accessible copy constructor. For a move-only
type an implementation is no longer bound to the "no-effects-on-failure"
constraint (It would be impossible to realize that), therefore uses the
move constructor irrespective whether it could throw an exception or not.

There is a simple way to realize that only your move-operations are
called instead of copy operations for type Both: Ensure that the move
constructor does not throw operations and mark it with the equivalent of
noexcept(true) or throw().

HTH & Greetings from Bremen,

Daniel Krügler

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Cassio Neri  
View profile  
 More options Oct 18 2012, 3:58 pm
Newsgroups: comp.lang.c++.moderated
From: Cassio Neri <cassio.n...@googlemail.com>
Date: Thu, 18 Oct 2012 12:57:55 -0700 (PDT)
Local: Thurs, Oct 18 2012 3:57 pm
Subject: Re: Move, std::vector and gcc: which is this correct?

> In other words, when both copy and move are defined explicitly, the
> copy constructor gets called but if the move constructor is
> defaulted, then it gets called in preference to the copy
> constructor.

> I would have expected the GCC 4.6 behaviour to be correct. Am I
> mistaken?

I think GCC 4.7 is correct and GCC 4.6 is not. I also believe (to be
checked) that the issue here is exception safety. When you increases
the vector capacity the elements must be copied or moved to the new
memory buffer. Let's consider the two possibilities:

1) Elements are copied.

That is, each element is copied into the new buffer. Suppose the
vector has successfully copied the first element to the new buffer and
now is copying the second element. What happens, from the exception
point of view, if the copy constructor throws? Only a copy of the
first element was made and the original is still there. Then, the
vector can deliver the strong exception guarantee.

2) Elements are moved.

That is, each element is moved into the new buffer. Suppose the vector
has successfully moved the first element to the new buffer and now is
moving the second element. What happens, from the exception point of
view, if the move constructor throws? The first element was moved from
its original position and is no longer there. As an attempt to stick to
the strong safety guarantee, the vector could try to move the first
element back from the new buffer to where it used to be. However, this
operation might also throw and things get worse. Therefore, in this
case, the vector cannot provide the strong safety guarantee.

What's is happening then? If T has just either a copy or a move
constructor, then vector<T> has no choice and must use what is
available. If both are available, vector<T> favors the strongest safe
guarantee over performance and will copy unless it knows that T's move
constructor doesn't throw.

I guess, in this particular case, the implicitly defined move
constructor is marked as noexcept (that is, it doesn't throw) whereas
the one provided by you is not.

Change Both's move constructor to:

Both(Both&&) noexcept
{
         cout << "Move\n";

}

then compile and run again. I think it GCC 4.7 will call your move
constructor this time.

A last not of caution: noexcept is not that simple. Hence, don't start
yet adding noexcept to all your functions that you ***believe*** don't
throw.  If you are wrong and the function does throw, then
std::terminate will be called. As far as I can tell, there's still a
lot of controversy on how to use noexcept safely. It might be worth
waiting a bit more when the gurus will come up with good guidelines
for us all.

HTH,
Cassio.

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
End of messages
« Back to Discussions « Newer topic     Older topic »