jeudi 1 décembre 2016

Does C++11 force move unconditionally?

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?

Aucun commentaire:

Enregistrer un commentaire