Implementing Move Semantics (class String
, Live Hacked)¶
Copy Semantics, Revisited¶
Copy semantics is complicated when resources are maintained (e.g. dynamic memory is allocated)
Compiler generated copy semantics is exactly counter-bugfree in such situations
⟶ pointers are copied, not the resources that they point to
class String
, Without Copy¶
class String
againCopy explicitly deleted (added again later)
⟶ neither copy nor move (remember: move is generated by compiler only when no dtor or copy is there
Implementation
#ifndef STRING_H #define STRING_H #include <cstring> class String { public: String() : _c_str(nullptr) {} String(const char* s) : _c_str(new char[strlen(s)+1]) { strcpy(_c_str, s); } ~String() { delete[] _c_str; } String(const String&) = delete; // <--- delete copy ctor String& operator=(const String&) = delete; // <--- delete copy assignment operator const char* c_str() const { return _c_str; } size_t size() const { return (_c_str == nullptr)? 0 : strlen(_c_str); } private: char* _c_str; }; #endif
Basic functionality/usage (other than copy/move)
#include <gtest/gtest.h> #include "string-10.h" TEST(string_move_suite, basic) { String s("Hello"); ASSERT_EQ(s.size(), 5); ASSERT_EQ(strcmp(s.c_str(), "Hello"), 0); }
Return From Function: Copy (Or Move) Wanted¶
Copy explicitly deleted
Rules: no move semantics either (we don’t know what that is anyway)
Return from function?
#include <gtest/gtest.h>
#include "string-10.h"
static String make_a_string_from(const char* s)
{
String ret_s(s);
return ret_s; // <--- copy constructor requested but deleted
}
TEST(string_move_suite, bogus)
{
String s = make_a_string_from("Hello"); // <--- copy constructor requested but deleted
}
No move, no copy
⟶ error
code/string-10-suite-bogus.cpp:8:12: error: use of deleted function ‘String::String(const String&)’
Move Constructor? Move?¶
String make_a_string_from(const char* s)
{
String ret_s(s);
return ret_s; // <--- proven: ret_s not accessible past this point
}
Local variable
ret_s
not accessible anymore afterreturn
, obviouslyNo need for a copy altogether which would be
Allocate destination (caller’s function value)
Copy source (
ret_s
to destination)Free source (
ret_s
)
⟶ enter move: transfer source to destination without copying
Compiler knows all that
⟶ can insert move constructor to transfer returned value to final location
Implementing Move Constructor¶
Prototype:
String(String&& from) noexcept
Transfers guts of
from
intothis
noexcept
is good practice but not requiredInvalidates
from
⟶ resources not deleted twiceAttention: invalidated object should still be usable!
#ifndef STRING_H
#define STRING_H
#include <cstring>
class String
{
public:
String() : _c_str(nullptr) {}
String(const char* s)
: _c_str(new char[strlen(s)+1])
{
strcpy(_c_str, s);
}
~String()
{
delete[] _c_str;
}
String(String&& from) noexcept // <--- move constructor
: _c_str(from._c_str) // <--- absorb from into this
{
from._c_str = nullptr; // <--- invalidate from
}
String(const String&) = delete;
String& operator=(const String&) = delete;
const char* c_str() const { return _c_str; }
size_t size() const { return (_c_str == nullptr)? 0 : strlen(_c_str); }
private:
char* _c_str;
};
#endif
#include <gtest/gtest.h>
#include "string-20.h"
static String make_a_string_from(const char* s)
{
String ret_s(s);
return ret_s; // <--- move-return
}
TEST(string_move_suite, move_return)
{
String s = make_a_string_from("Hello"); // <--- move-initialization
}
Note
The manual operations (move _c_str
from there to here, and
invalidated there) can also be written as
#include <utility>
String(String&& from) noexcept
: _c_str(std::exchange(from._c_str, nullptr) {}
And Move Assignment?¶
#include <gtest/gtest.h>
#include "string-20.h"
static String make_a_string_from(const char* s)
{
String ret_s(s);
return ret_s;
}
TEST(string_move_suite, move_assignment)
{
String s;
s = make_a_string_from("Hello"); // <--- copy assignment requested but deleted
}
(Copy) assignment was deleted
No move assignment
⟶ no assigment
code/string-20-suite-bogus.cpp:14:35: error: use of deleted function ‘String& String::operator=(const String&)’
Implementing Move Assignment¶
Prototype:
String& operator=(String&&) noexcept
Transfers guts, and invalidates source (just like move constructor)
Much like copy assignment ⟶ self assignment check
#ifndef STRING_H
#define STRING_H
#include <utility>
#include <cstring>
class String
{
public:
String() : _c_str(nullptr) {}
String(const char* s)
: _c_str(new char[strlen(s)+1])
{
strcpy(_c_str, s);
}
~String()
{
delete[] _c_str;
}
String(String&& from) noexcept
: _c_str(std::exchange(from._c_str, nullptr)) {}
String(const String&) = delete;
String& operator=(const String&) = delete;
String& operator=(String&& from) noexcept
{
if (this != &from) { // <--- self assignment check
delete[] _c_str;
_c_str = std::exchange(from._c_str, nullptr);
}
return *this;
}
const char* c_str() const { return _c_str; }
size_t size() const { return (_c_str == nullptr)? 0 : strlen(_c_str); }
private:
char* _c_str;
};
#endif