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:
s(const s &other)
.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?
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.
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!
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.
No move constructor is automatically generated.
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.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With