Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does C++11 force move unconditionally?

Tags:

c++

gcc

c++11

I compile the following code using command g++ -std=c++11 t.cpp:

#include <vector>
#include <cstring> //memcpy()
class s
{
    char *p;
    size_t size;
public:
    s(){
        size=10;
        p=new char[size];
    }
    s(const s &other){
        size=other.size;
        p=new char[size];
        memcpy(p,other.p,other.size);
    }
    ~s(){ delete [] p; }
};

int main()
{
    std::vector<s> ss;
    ss.push_back(s());
}

This is gdb log:

Breakpoint 1, main () at t.cpp:23
23          ss.push_back(s());
s::s (this=0x7fffffffe370) at t.cpp:9
9               size=10;
10              p=new char[size];
11          }
std::vector<s, std::allocator<s> >::push_back(s&&) (this=0x7fffffffe350, 
    __x=<unknown type in /tmp/a.out, CU 0x0, DIE 0x20d0>)
    at /usr/include/c++/4.9/bits/stl_vector.h:932
932       { emplace_back(std::move(__x)); }
std::move<s&> (__t=...) at /usr/include/c++/4.9/bits/move.h:102
102     { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
std::vector<s, std::allocator<s> >::emplace_back<s>(s&&) (this=0x7fffffffe350)
    at /usr/include/c++/4.9/bits/vector.tcc:94
94      if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
101       _M_emplace_back_aux(std::forward<_Args>(__args)...);
102       }
s::~s (this=0x7fffffffe370, __in_chrg=<optimized out>) at t.cpp:17
17          ~s(){ delete [] p; }
std::vector<s, std::allocator<s> >::~vector (this=0x7fffffffe350, __in_chrg=<optimized out>)
    at /usr/include/c++/4.9/bits/stl_vector.h:425

From the log, I am in the following impression:

  1. GCC ignores copy constructor s(const s &other).
  2. GCC automatically creates a move constructor for class s and then std::vector.push_back() calls that move constructor.

    This article states

    Thus, if the definition of class C doesn't explicitly declare a move assignment operator, one will be implicitly declared as defaulted only if all of the following conditions are met:

    My question here is that class s apparently has a user-declared destructor ~s(), meaning class s does not meet the forth condition and thus GCC should not enforce move on std::vector.push_back(). How does GCC behaves so?

  3. Destructor ~s() is called two times: first immediately after temporary s() has been passed as argument to ss.push_back(s()); and moved, and second after the destructor of std::vector<s> is called.

  4. Because this code does not meet rule of three, it is doomed to crash when the destructor of std::vector<s> is called. The content pointed to by p in object s is already deleted by the first ~s(). Therefore, std::vector<s> destructor calling ~s() must crash with error like double free or corruption.

However, much to my surprise, somehow this program runs and terminates normally.

question 1: Why the program does not crash? Am I simply lucky?

question 2: According to gdb log, does it mean that codes designed for prior C++11 meeting rule of three but not meeting rule of five, like this example, are very likely to crash when they are compiled to C++11 executables?

EDIT:

This question was raised due to my misinterpretation of gdb messages like these:

std::vector<s, std::allocator<s> >::push_back(s&&)
emplace_back(std::move(__x));
std::vector<s, std::allocator<s> >::emplace_back<s>(s&&)

which lead me to think that GCC creates a move constructor. I just did as many these experts told me to - setting a break point in s(const s &other), and noticed that the program does stop there. This finding invalidates all my questions.

As every expert here suggested, the facts are: 1. GCC does not create any move constructor. 2. The existing copy constructor is called.

Thank you all for the big helps!

like image 402
Masao Liu Avatar asked Dec 01 '16 09:12

Masao Liu


People also ask

Does STD forward move?

C++ std::move and std::forward. C++ std::move does not move and std::forward does not forward. This article dives deep into a long list of rules on lvalues, rvalues, references, overloads and templates to be able to explain a few deceivingly simple lines of code using std::move and std::forward.

Is move constructor automatically generated?

No move constructor is automatically generated.

Why we need std move?

std::move itself does "nothing" - it has zero side effects. It just signals to the compiler that the programmer doesn't care what happens to that object any more. i.e. it gives permission to other parts of the software to move from the object, but it doesn't require that it be moved.

What is std move?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.


1 Answers

GCC ignores copy constructor s(const s &other).

It may well do that, as a copy-elision optimization.

GCC automatically creates a move constructor for class s and then std::vector.push_back() calls that move constructor.

No. A move constructor is not generated.

My question here is that class s apparently has a user-declared destructor ~s(), meaning class s does not meet the forth condition

Correct. The class also doesn't meet the first condition (user declared copy constructor).

How does GCC behaves so?

GCC seems to behave as it should. There is no move construction or assignment involved.

Destructor ~s() is called two times

In the gdb log that you show, I only see one instance of s::~s being called.

question 1: Why the program does not crash? Am I simply lucky?

I would consider you unlucky. It seems that push_back didn't use the copy assignment operator, so the conditions for double free didn't occur.

question 2: According to gdb log, does it mean that codes designed for prior C++11 meeting rule of three but not meeting rule of five, like this example...

This example doesn't meet the rule of three, so this appears not to be example of what you're asking for.

are very likely to crash when they are compiled to C++11 executables?

No. As long as code follows the rule of three, it is safe to copy the objects. Following the rule of five is only necessary in order to make the object movable (to avoid copying).

like image 170
eerorika Avatar answered Sep 21 '22 10:09

eerorika