c++ - Best form for constructors? Pass by value or reference? -


i'm wondering best form constructors. here sample code:

class y { ... }  class x { public:   x(const y& y) : m_y(y) {} // (a)   x(y y) : m_y(y) {} // (b)   x(y&& y) : m_y(std::forward<y>(y)) {} // (c)    y m_y; }  y f() { return ... }  int main() {   y y = f();   x x1(y); // (1)   x x2(f()); // (2) } 

from understand, best compiler can in each situation.

(1a) y copied x1.m_y (1 copy)

(1b) y copied argument of constructor of x, , copied x1.m_y (2 copies)

(1c) y moved x1.m_y (1 move)

(2a) result of f() copied x2.m_y (1 copy)

(2b) f() constructed argument of constructor, , copied x2.m_y (1 copy)

(2c) f() created on stack, , moved x2.m_y (1 move)

now few questions:

  1. on both counts, pass const reference no worse, , better pass value. seems go against discussion on "want speed? pass value.". c++ (not c++0x), should stick pass const reference these constructors, or should go pass value? , c++0x, should pass rvalue reference on pass value?

  2. for (2), i'd prefer if temporary constructed directly x.m_y. rvalue version think requires move which, unless object allocates dynamic memory, work copy. there way code compiler permitted avoid these copies , moves?

  3. i've made lot of assumptions in both think compiler can best , in questions themselves. please correct of these if incorrect.

i've thrown examples. used gcc 4.4.4 in of this.

simple case, without -std=c++0x

first, put simple example 2 classes accept std::string each.

#include <string> #include <iostream>  struct /* construct reference */   {     std::string s_;      (std::string const &s) : s_ (s)       {         std::cout << "a::<constructor>" << std::endl;       }     (a const &a) : s_ (a.s_)       {         std::cout << "a::<copy constructor>" << std::endl;       }     ~a ()       {         std::cout << "a::<destructor>" << std::endl;       }   };  struct b /* construct value */   {     std::string s_;      b (std::string s) : s_ (s)       {         std::cout << "b::<constructor>" << std::endl;       }     b (b const &b) : s_ (b.s_)       {         std::cout << "b::<copy constructor>" << std::endl;       }     ~b ()       {         std::cout << "b::<destructor>" << std::endl;       }   };  static f () { return ("string"); } static f2 () { a ("string"); a.s_ = "abc"; return a; } static b g () { return b ("string"); } static b g2 () { b b ("string"); b.s_ = "abc"; return b; }  int main ()   {     a (f ());     a2 (f2 ());     b b (g ());     b b2 (g2 ());      return 0;   } 

the output of program on stdout follows:

a::<constructor> a::<constructor> b::<constructor> b::<constructor> b::<destructor> b::<destructor> a::<destructor> a::<destructor> 

conclusion

gcc able optimize each , every temporary a or b away. consistent c++ faq. basically, gcc may (and willing to) generate code constructs a, a2, b, b2 in place, if function called appearantly returns value. thereby gcc can avoid many of temporaries existence 1 might have "inferred" looking @ code.

the next thing want see how std::string copied in above example. let's replace std::string can observe better , see.

realistic case, without -std=c++0x

#include <string> #include <iostream>  struct s   {     std::string s_;      s (std::string const &s) : s_ (s)       {         std::cout << "  s::<constructor>" << std::endl;       }     s (s const &s) : s_ (s.s_)       {         std::cout << "  s::<copy constructor>" << std::endl;       }     ~s ()       {         std::cout << "  s::<destructor>" << std::endl;       }   };  struct /* construct reference */   {     s s_;      (s const &s) : s_ (s) /* expecting 1 copy here */       {         std::cout << "a::<constructor>" << std::endl;       }     (a const &a) : s_ (a.s_)       {         std::cout << "a::<copy constructor>" << std::endl;       }     ~a ()       {         std::cout << "a::<destructor>" << std::endl;       }   };  struct b /* construct value */   {     s s_;      b (s s) : s_ (s) /* expecting 2 copies here */       {         std::cout << "b::<constructor>" << std::endl;       }     b (b const &b) : s_ (b.s_)       {         std::cout << "b::<copy constructor>" << std::endl;       }     ~b ()       {         std::cout << "b::<destructor>" << std::endl;       }   };  /* expecting total of 1 copy of s here */ static f () { s s ("string"); return (s); }  /* expecting total of 1 copy of s here */ static f2 () { s s ("string"); s.s_ = "abc"; a (s); a.s_.s_ = "a"; return a; }  /* expecting total of 2 copies of s here */ static b g () { s s ("string"); return b (s); }  /* expecting total of 2 copies of s here */ static b g2 () { s s ("string"); s.s_ = "abc"; b b (s); b.s_.s_ = "b"; return b; }  int main ()   {     a (f ());     std::cout << "" << std::endl;     a2 (f2 ());     std::cout << "" << std::endl;     b b (g ());     std::cout << "" << std::endl;     b b2 (g2 ());     std::cout << "" << std::endl;      return 0;   } 

and output, unfortunately, meets expectation:

  s::<constructor>   s::<copy constructor> a::<constructor>   s::<destructor>    s::<constructor>   s::<copy constructor> a::<constructor>   s::<destructor>    s::<constructor>   s::<copy constructor>   s::<copy constructor> b::<constructor>   s::<destructor>   s::<destructor>    s::<constructor>   s::<copy constructor>   s::<copy constructor> b::<constructor>   s::<destructor>   s::<destructor>  b::<destructor>   s::<destructor> b::<destructor>   s::<destructor> a::<destructor>   s::<destructor> a::<destructor>   s::<destructor> 

conclusion

gcc not able optimize away temporary s created b's constructor. using default copy constructor of s did not change that. changing f, g be

static f () { return (s ("string")); } // still 1 copy static b g () { return b (s ("string")); } // reduced 1 copy! 

did have indicated effect. appears gcc willing construct argument b's constructor in place hesitant construct b's member in place. note still no temporary a or b created. means a, a2, b, b2 still being constructed in place. cool.

let's investigate how new move semantics may influence second example.

realistic case, -std=c++0x

consider adding following constructor s

    s (s &&s) : s_ ()       {         std::swap (s_, s.s_);         std::cout << "  s::<move constructor>" << std::endl;       } 

and changing b's constructor

    b (s &&s) : s_ (std::move (s)) /* how many copies?? */       {         std::cout << "b::<constructor>" << std::endl;       } 

we output

  s::<constructor>   s::<copy constructor> a::<constructor>   s::<destructor>    s::<constructor>   s::<copy constructor> a::<constructor>   s::<destructor>    s::<constructor>   s::<move constructor> b::<constructor>   s::<destructor>    s::<constructor>   s::<move constructor> b::<constructor>   s::<destructor>  b::<destructor>   s::<destructor> b::<destructor>   s::<destructor> a::<destructor>   s::<destructor> a::<destructor>   s::<destructor> 

so, able replace four copies two moves using pass rvalue.

but constructed broken program.

recall g, g2

static b g ()  { s s ("string"); return b (s); } static b g2 () { s s ("string"); s.s_ = "abc"; b b (s); /* s zombie */ b.s_.s_ = "b"; return b; } 

the marked location shows problem. move done on object not temporary. that's because rvalue references behave lvalue references except may bind temporaries. must not forget overload b's constructor 1 takes constant lvalue reference.

    b (s const &s) : s_ (s)       {         std::cout << "b::<constructor2>" << std::endl;       } 

you notice both g, g2 cause "constructor2" called, since symbol s in either case better fit const reference rvalue reference. can persuade compiler move in g in either of 2 ways:

static b g ()  { return b (s ("string")); } static b g ()  { s s ("string"); return b (std::move (s)); } 

conclusions

do return-by-value. code more readable "fill reference give you" code and faster and maybe more exception safe.

consider changing f to

static void f (a &result) { tmp; /* ... */ result = tmp; } /* or */ static void f (a &result) { /* ... */ result = (s ("string")); } 

that meet strong guarantee if a's assignment provides it. copy result cannot skipped, neither can tmp constructed in place of result, since result not being constructed. thus, slower before, no copying necessary. c++0x compilers , move assignment operators reduce overhead, it's still slower return-by-value.

return-by-value provides strong guarantee more easily. object constructed in place. if 1 part of fails , other parts have been constructed, normal unwinding clean and, long s's constructor fulfills basic guarantee regard own members , strong guarantee regard global items, whole return-by-value process provides strong guarantee.

always pass value if you're going copy (onto stack) anyway

as discussed in want speed? pass value.. compiler may generate code constructs, if possible, caller's argument in place, eliminating copy, cannot when take reference , copy manually. principal example: not write (taken cited article)

t& t::operator=(t const& x) // x reference source {      t tmp(x);          // copy construction of tmp hard work     swap(*this, tmp);  // trade our resources tmp's     return *this;      // our (old) resources destroyed tmp  } 

but prefer this

t& t::operator=(t x)    // x copy of source; hard work done {     swap(*this, x);  // trade our resources x's     return *this;    // our (old) resources destroyed x } 

if want copy non-stack frame location pass const reference pre c++0x , additionally pass rvalue reference post c++0x

we saw this. pass reference causes less copies take place when in place construction impossible pass value. , c++0x's move semantics may replace many copies fewer , cheaper moves. keep in mind moving make zombie out of object has been moved from. moving not copying. providing constructor accepts rvalue references may break things, shown above.

if want copy non-stack frame location , have swap, consider passing value anyway (pre c++0x)

if have cheap default construction, combined swap may more efficient copying stuff around. consider s's constructor be

    s (std::string s) : s_ (/* cheap std::string? */)       {         s_.swap (s); /* may faster copying */         std::cout << "  s::<constructor>" << std::endl;       } 

Comments

Popular posts from this blog

asp.net - repeatedly call AddImageUrl(url) to assemble pdf document -

java - Android recognize cell phone with keyboard or not? -

iphone - How would you achieve a LED Scrolling effect? -