4

Suppose I'm coding a string class in C++ (I know I can use the library). The string length is variable and the storage space is dynamically allocated in the constructor and freed in the destructor. When the main function calls c=a+b (a,b,c are strings), the operator+ member function creates a temporary object that stores the concatenated string a+b, returns it to the main function, and then the operator= member function is called to free the string originally stored in c and copy data from the temporary string a+b to c, and finally the temporary a+b is destructed.

I'm wondering if there's a way to make this happen: instead of having the operator= copy data from a+b to c, I want it to swap the data pointers of a+b and c, so that when a+b is destructed, it destructs the original data in c (which is what we want), while c now takes the result of a+b without needing to copy.

I know coding a 2-parameter member function setToStrcat and calling c.setToStrcat(a,b) can do this. For example, the function can be coded as:

    void String::setToStrcat(const String& a,const String& b){
      String tmp(a.len+b.len); int i,j;
      for(i=0;i<a.len;i++) tmp[i]=a[i];
      for(j=0;j<b.len;i++,j++) tmp[i]=b[j];
      tmp[i]='\0'; this->swap(tmp);
    }

    void String::swap(String& a){
      int n=len; len=a.len; a.len=n;
      char *s=str; str=a.str; a.str=s;
    }

I omitted the definitions of my constructor (which allocates len+1 char-type spaces) and operator[] (which returns a reference of the ith character). The swap function swaps the data pointers and length variables between *this and tmp, so that when tmp is destructed after the swap, it is actually the data originally stored in *this (the String c in the main function) that is destructed. What *this now has in its possession (c.str) is the concatenated string a+b.

I would like to know if there is a way to optimize the performance of c=a+b to the same level. I tried c.swap(a+b) and changed the return type of a+b to String&, but I receive warning (reference to a local variable) and GDB shows that the temporary gets destructed before the swap happens, while I want the other way.

I think my question is general. In C++ programming, we often need a temporary object to store the result of a function, but when we assign it to another object in the main function, can we not copy the data but use a (much faster) swap of pointers instead? What is a neat way of making this happen?

Zhuoran He
  • 873
  • 9
  • 15
  • 4
    There is a neat way :) Search for move semantics, more specifically: move constructors and move assignment operator. – Rakete1111 Aug 24 '16 at 04:27
  • 2
    Haven't heard of move semantics yet? [Read up.](https://www.google.com/search?q=c%2B%2B+move+semantics) – user2357112 Aug 24 '16 at 04:27
  • 2
    What you are talking about looks like [copy elision](http://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization). In C++ it will become guaranteed since C++17. – Sergey Aug 24 '16 at 04:33
  • Helpful links: [move constructor](http://en.cppreference.com/w/cpp/language/move_constructor) and [move assignment operator](http://en.cppreference.com/w/cpp/language/move_assignment). – R Sahu Aug 24 '16 at 04:33
  • In my compiler, if I do `String c=a+b;` the `operator=` is not called (a byte-by-byte copy of the pointer value and length variable was done instead, I guess). This is copy elision. But if I do `String c; c=a+b;`, the `operator=` is called. This is correct, because otherwise the original data in c (the empty string) is not freed. All I want is a swap-based copy (swapping data pointers of `a+b` and `c`), rather than no copy (which would be incorrect in this case). – Zhuoran He Aug 24 '16 at 14:35

1 Answers1

1

In C++11, you can do this by writing a move constructor. Rvalue references were added to the language to solve this exact problem.

class String {
  ...
  String(String&& s) : str(nullptr) {
    this->swap(s);
  }
  String& operator=(String&& s) {
    this->swap(s);
  }
  ...
  String operator+(String const& other) {
    // (your implementation of concatenation here)
  }
  ...
}

Then code like this will not trigger an extra copy constructor or a memory allocation, it will just move the allocated memory from the temporary (the thing returned from operator+) to the new object c.

String c = a + b;
Krzysztof Kosiński
  • 4,225
  • 2
  • 18
  • 23
  • `String c = a + b;` may perform copy elision anyway – M.M Aug 24 '16 at 05:18
  • I see. The temporary string `a+b` is an rvalue. To refer to it, I should use an rvalue reference. The `operator=` of the `String` class can be 2-fold overloaded, one taking const lvalue references and doing deep copy, and the other taking rvalue references and doing swap of pointers. When the compiler cannot decide which one to call, use the `std::move` semantics to explicitly convert to rvalues. One more question to make sure: is the `a+b` still destructed after `c=a+b`? – Zhuoran He Aug 24 '16 at 13:32
  • Yes, it's destructed, but it contains `nullptr`, so it does not deallocate anything. – Krzysztof Kosiński Aug 24 '16 at 18:34
  • I tried it in c++11. It worked just as I wanted. Thank you! One can either swap `a+b` with `c` and let the destructor of `a+b` deallocate the old data of `c`, or deallocate `c` in the overloaded `operator=` that takes rvalue references, copy the pointer of `a+b` to `c`, and set the pointer of `a+b` to `nullptr` (or 0) to tell the destructor not to do anything. In C++ programming, the less "magical" way is always better. – Zhuoran He Aug 24 '16 at 23:30