c++ - A canonical way to implement move semantics in a class -


i looking example of how implement derived , base class move semantics. more have thought it, more seems default move constructor , assignment move operator job because standard (stl) types , smart pointers default moveable.

anyway, in case have class hierarchy, requires explicit move implementation, how should - @ least first cut?

in example, using raw pointer wrap in std::unique_ptr needed example of move not default moveable.

any appreciated. :)

currently, have made following attempt:

struct bloba {     char data[0xaa]; };  struct blobb {     char data[0xbb]; };  //----------------------------------------  class base { public:      //default construct base class     //c++11 allows data members initialised, declared. otherwise here.     base()     {      }      //define destructor virtual ensure derived destructor gets called. (in case necessary. it's not in example still practice.)     virtual ~base()     {         delete m_moveabledatainbase; //this contrived example show non-default moveable data, in practice raii should used rather deletes     }      //copy constructor, needs accept const ref base class copies data members     base(const base& other) :     m_moveabledatainbase(new bloba(*other.m_moveabledatainbase)), // copy other's moveable data     m_pnonmoveabledatainbase(other.m_pnonmoveabledatainbase)        // copy other's non-moveable data     {      }      //assignment operator uses canonical copy swap idiom. returns reference allow chaining: = b = c     //this implemented in terms of copy constructor.     base& operator=(const base& rhs)     {         base temp(rhs);         swap(temp);         return *this;     }      //the move construtor declared throwing no exceptions called stl algorithms     //it accepts rvalue , moves base part.     base(base&& other) noexcept :     m_moveabledatainbase(nullptr) // don't bother allocating our own resources moveable data because move (steal) other's resource     {         swap(other);     }      //the move assignment operator declared throwing no exceptions called stl algorithms     //it accepts rvalue , moves (steals) data resources rhs using swap , copies non moveable data rhs     base& operator=(base&& rhs) noexcept     {         //move (steal) moveable contents rhs         std::swap(m_moveabledatainbase, rhs.m_moveabledatainbase);          //copy non-moveable contents rhs         m_pnonmoveabledatainbase = rhs.m_pnonmoveabledatainbase;          return *this;     }  private:     //this private member swaps data members' contents.     //it private because isn't virtual , swaps base contents , not safe public interface     void swap(base& other)     {         std::swap(m_moveabledatainbase, other.m_moveabledatainbase);         std::swap(m_pnonmoveabledatainbase, other.m_pnonmoveabledatainbase);     }      //an example of large blob of data move instead of copy performance reasons.     //normally, have used unique_ptr default moveable , need example of isn't     bloba* m_moveabledatainbase{ new bloba };      //an example of data can't or don't want move     int m_pnonmoveabledatainbase = 123; };  //----------------------------------------  class derived : public base { public:      //default construct derived class, called after base class constructor     //c++11 allows data members initialised, declared. otherwise here.     derived()     {      }      //default virtual destructor, clean stuff can't done automatically through raii     virtual ~derived()     {         delete m_pmoveabledatainderived; //this contrived example show non-default moveable data, in practice raii should used rather deletes     }      //copy constructor, needs accept const ref derived class     //first copy constructs base , copies derived data members     derived(const derived& other) :     base(other),  // forward base copy constructor     m_pmoveabledatainderived(new blobb(*other.m_pmoveabledatainderived)),  // copy other's moveable data     m_pnonmoveabledatainderived(other.m_pnonmoveabledatainderived)         // copy other's non-moveable data     {      }      //assignment operator uses canonical copy swap idiom. returns reference allow chaining: = b = c     //because uses derived copy constructor, in turn copy constructs base, don't forward base assignment operator.     derived& operator=(const derived& rhs)     {         derived temp(rhs);         swap(temp);         return *this;     }      //the move construtor declared throwing no eceptions called stl algorithms     //it accepts rvalue , first moves base part , derived part.     //there no point in allocating resource before moving in example, m_pblobb set nullptr     derived(derived&& other) noexcept     : base(std::move(other)), // forward base move constructor     m_pmoveabledatainderived(nullptr) // don't bother allocating our own resources moveable data because move (steal) other's resource     {         swap(other);     }      //the move assignment operator declared throwing no exceptions called stl algorithms     //it accepts rvalue , first calls base assignment operator , moves data resources rhs using swap     derived& operator=(derived&& rhs) noexcept     {         //forward base move operator=         base::operator=(std::move(rhs));          //move (steal) moveable contents rhs         std::swap(m_pmoveabledatainderived, rhs.m_pmoveabledatainderived);          //copy non-moveable contents rhs         m_pnonmoveabledatainderived = rhs.m_pnonmoveabledatainderived;     }  private:     //this member swaps derived data members contents.     //it private because doesn't swap base contents , not safe public interface     void swap(derived& other) noexcept     {         std::swap(m_pmoveabledatainderived, other.m_pmoveabledatainderived);         std::swap(m_pnonmoveabledatainderived, other.m_pnonmoveabledatainderived);     }      //an example of large blob of data move instead of copy performance reasons.     //normally, have used unique_ptr default moveable , need example of isn't     blobb* m_pmoveabledatainderived{ new blobb };      //an example of data can't or don't want move     int m_pnonmoveabledatainderived = 456; }; 

you have start out knowing class invariants are. class invariant or relationship true among data members. , want make sure special members can operate value satisfies class invariants. special members should not have preconditions (except class invariants must true).

let's take example example case discuss. first lets concentrate on base. put private data members front close special members. way can more see defaulted or implicitly declared special members do.

base

class base {     //an example of large blob of data move      //  instead of copy performance reasons.     //normally, have used unique_ptr default moveable     //   , need example of isn't     bloba* m_moveabledatainbase{ new bloba };      //an example of data can't or don't want move     int m_pnonmoveabledatainbase = 123; 

so far good, there slight ambiguity here: can m_moveabledatainbase == nullptr? there no 1 right or wrong answer. question author of base must answer, , write code enforce.

also, outline member functions. if decide want inline them, outside declaration. otherwise class declaration becomes difficult read:

class base {     bloba* m_moveabledatainbase{ new bloba };     int m_pnonmoveabledatainbase = 123;  public:     virtual ~base();     base() = default;     base(const base& other);     base& operator=(const base& rhs);     base(base&& other) noexcept;     base& operator=(base&& rhs) noexcept; }; 

the destructor telling special member. declared/defined first:

base::~base() {     delete m_moveabledatainbase; } 

this looks good. not yet answer question whether m_moveabledatainbase can nullptr. next, if exists, default constructor. prefer = default definitions when practical.

now copy constructor:

base::base(const base& other)     : m_moveabledatainbase(new bloba(*other.m_moveabledatainbase))     , m_pnonmoveabledatainbase(other.m_pnonmoveabledatainbase) { } 

ok, says significant:

other.m_moveabledatainbase != nullptr  // ever 

i glanced ahead , looked @ move constructor, , leaves moved-from value m_moveabledatainbase == nullptr. have problem:

  1. either there bug in copy constructor, , should check case other.m_moveabledatainbase == nullptr, or

  2. there bug in move constructor , should not leave moved-from state m_moveabledatainbase == nullptr.

neither solution correct one. base author has make design decision. if choses 2, there no reasonable way implement move constructor such faster copy constructor. in case thing not write move constructor , let copy constructor job. i'll choose 1 there still move constructor talk about. corrected copy constructor:

base::base(const base& other)     : m_moveabledatainbase(other.m_moveabledatainbase ?                            new bloba(*other.m_moveabledatainbase) :                            nullptr)     , m_pnonmoveabledatainbase(other.m_pnonmoveabledatainbase) { } 

also, since chose invariant, might not bad idea revisit default constructor , instead say:

    bloba* m_moveabledatainbase = nullptr; 

now have noexcept default constructor.

next comes copy assignment operator. not fall trap of selecting copy/swap idiom default. idiom fine. poorly performing. and performance more important code reuse. consider alternative copy/swap:

base& base::operator=(const base& rhs) {     if (this != &rhs)     {         if (m_moveabledatainbase == nullptr)         {             if (rhs.m_moveabledatainbase != nullptr)                 m_moveabledatainbase = new bloba(*rhs.m_moveabledatainbase);         }         else  // m_moveabledatainbase != nullptr         {             if (rhs.m_moveabledatainbase != nullptr)                 *m_moveabledatainbase = *rhs.m_moveabledatainbase;             else             {                 delete m_moveabledatainbase;                 m_moveabledatainbase = nullptr;             }         }         m_pnonmoveabledatainbase = rhs.m_pnonmoveabledatainbase;     }     return *this; } 

if common values of base have m_moveabledatainbase != nullptr, rewrite faster copy/swap. in common case, copy/swap 1 new , 1 delete. version 0 news , 0 deletes. copies 170 bytes.

and if had chosen design invariant m_moveabledatainbase != nullptr, copy assignment gets simpler:

base& base::operator=(const base& rhs) {     *m_moveabledatainbase = *rhs.m_moveabledatainbase;     m_pnonmoveabledatainbase = rhs.m_pnonmoveabledatainbase;     return *this; } 

minimizing calls heap not premature optimization. engineering. move semantics made out of. precisely why std::vector , std::string copy assignment not use copy/swap idiom. slow.

move constructor: code this:

base::base(base&& other) noexcept     : m_moveabledatainbase(std::move(other.m_moveabledatainbase))     , m_pnonmoveabledatainbase(std::move(other.m_pnonmoveabledatainbase)) {     other.m_moveabledatainbase = nullptr; } 

this saves few loads , stores. didn't bother inspecting generated assembly. urge before choosing implementation on one. in noexcept move constructor, count loads , stores.

as style-guide, move members when know scalars , move has no impact. saves reader of code having ensure non-moved members scalars.

your move assignment looks fine me:

base& base::operator=(base&& rhs) noexcept {     //move (steal) moveable contents rhs     std::swap(m_moveabledatainbase, rhs.m_moveabledatainbase);     //copy non-moveable contents rhs     m_pnonmoveabledatainbase = rhs.m_pnonmoveabledatainbase;     return *this; } 

the 1 time when don't want when have non-memory resources on lhs need destructed immediately, opposed swapped rhs. example swapping memory.

derived

for derived write i've shown base, except first copying/moving base show in code. example here move constructor:

derived::derived(derived&& other) noexcept     : base(std::move(other))     , m_pmoveabledatainderived(std::move(other.m_pmoveabledatainderived))     , m_pnonmoveabledatainderived(std::move(other.m_pnonmoveabledatainderived)) {     other.m_pmoveabledatainderived = nullptr; } 

also tag ~dervied() override instead of virtual. want compiler tell if you've accidentally somehow not overridden ~base() ~derived().

class derived : public base {     blobb* m_pmoveabledatainderived = nullptr;     int m_pnonmoveabledatainderived = 456;  public:     ~derived() override;     derived() = default;     derived(const derived& other);     derived& operator=(const derived& rhs);     derived(derived&& other) noexcept;     derived& operator=(derived&& rhs) noexcept; }; 

test

also test 6 special members (whether have them or not) static_assert , type-traits:

static_assert(std::is_nothrow_destructible<base>{}, ""); static_assert(std::is_nothrow_default_constructible<base>{}, ""); static_assert(std::is_copy_constructible<base>{}, ""); static_assert(std::is_copy_assignable<base>{}, ""); static_assert(std::is_nothrow_move_constructible<base>{}, ""); static_assert(std::is_nothrow_move_assignable<base>{}, "");  static_assert(std::is_nothrow_destructible<derived>{}, ""); static_assert(std::is_nothrow_default_constructible<derived>{}, ""); static_assert(std::is_copy_constructible<derived>{}, ""); static_assert(std::is_copy_assignable<derived>{}, ""); static_assert(std::is_nothrow_move_constructible<derived>{}, ""); static_assert(std::is_nothrow_move_assignable<derived>{}, ""); 

you can test these blob types:

static_assert(std::is_trivially_destructible<bloba>{}, ""); static_assert(std::is_trivially_default_constructible<bloba>{}, ""); static_assert(std::is_trivially_copy_constructible<bloba>{}, ""); static_assert(std::is_trivially_copy_assignable<bloba>{}, ""); static_assert(std::is_trivially_move_constructible<bloba>{}, ""); static_assert(std::is_trivially_move_assignable<bloba>{}, "");  static_assert(std::is_trivially_destructible<blobb>{}, ""); static_assert(std::is_trivially_default_constructible<blobb>{}, ""); static_assert(std::is_trivially_copy_constructible<blobb>{}, ""); static_assert(std::is_trivially_copy_assignable<blobb>{}, ""); static_assert(std::is_trivially_move_constructible<blobb>{}, ""); static_assert(std::is_trivially_move_assignable<blobb>{}, ""); 

summary

in summary, give each of 6 special members loving care deserve, if result inhibit them, implicitly declare them, or explicitly default or delete them. complier-generated move members move each base, move each non-static data member. prefer recipe, default when can, , augment when necessary.

highlight class api moving member function definitions out of class declaration.

test. @ least test or not have 6 special members, , if have them, if noexcept or trivial (or not).

use copy/swap caution. can performance killer.


Comments

Popular posts from this blog

get url and add instance to a model with prefilled foreign key :django admin -

css - Make div keyboard-scrollable in jQuery Mobile? -

ruby on rails - Seeing duplicate requests handled with Unicorn -