Search code examples
c++copy-constructorrvo

Force use of copy constructor / Avoid use of copy constructor


I'm currently writing a logging class (just for practice) and ran into an issue. I have two classes: The class Buffer acts as a temporary buffer and flushes itself in it's destructor. And the class Proxy that returns a Buffer instance, so I don't have to write Buffer() all the time.

Anyways, here is the code:

#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

class Buffer
{
private:
  std::stringstream buf;
public:
  Buffer(){};
  template <typename T>
  Buffer(const T& v)
  {
    buf << v;
    std::cout << "Constructor called\n";
  };
  ~Buffer()
  {
    std::cout << "HEADER: " << buf.str() << "\n";
  }
  Buffer(const Buffer& b)
  {
    std::cout << "Copy-constructor called\n";
    // How to get rid of this?
  };
  Buffer(Buffer&&) = default;
  Buffer& operator=(const Buffer&) & = delete;
  Buffer& operator=(Buffer&&) & = delete;
  template <typename T>
  Buffer& operator<<(const T& v)
  {
    buf << v;
    return *this;
  }
};

class Proxy
{
public:
  Proxy(){};
  ~Proxy(){};
  Proxy(const Proxy&) = delete;
  Proxy(Proxy&&) = delete;
  Proxy& operator=(const Proxy&) & = delete;
  Proxy& operator=(Proxy&&) & = delete;
  template <typename T>
  Buffer operator<<(const T& v) const
  {
    if(v < 0)
      return Buffer();
    else
      return Buffer(v);
  }
};

int main () {  
  Buffer(Buffer() << "Test") << "what";
  Buffer() << "This " << "works " << "fine";
  const Proxy pr;
  pr << "This " << "doesn't " << "use the copy-constructor";
  pr << "This is a " << std::setw(10) << " test";
  return 0;
}

Here is the output:

Copy-constructor called
HEADER: what
HEADER: Test
HEADER: This works fine
Constructor called
HEADER: This doesn't use the copy-constructor
Constructor called
HEADER: This is a       test

The code does exactly what I want but it depends on RVO. I read multiple times that you should not rely on RVO so I wanted to ask how I can:

  1. Avoid RVO completely so that the copy constructor is called every time
  2. Avoid the copy constructor

I already tried to avoid the copy constructor by returning a reference or moving but that segfaults. I guess thats because the temporary in Proxy::operator<< is deleted during return.

I'd also be interested in completely different approaches that do roughly the same.


Solution

  • This seems like a contrived problem: Firstly, the code works whether RVO is enabled or disabled (you can test it by using G++ with the no-elide-constructors flag). Secondly, the way you are designing the return of a Buffer object for use with the << operator can only be done by copying: The Proxy::operator<<(const T& v) function creates a new Buffer instance on the stack, which is then deleted when you leave the function call (i.e. between each concatenation in pr << "This " << "doesn't " << "use the copy-constructor";); This is why you get a segmentation fault when trying to reference this object from outside the function.

    Alternatively, you could define a << operator to use dynamic memory by e.g. returning a unique_ptr<Buffer>:

    #include <memory>
    
    ...
    
    std::unique_ptr<Buffer> operator<<(const T& v) const
    {
        if(v < 0)
            return std::unique_ptr<Buffer>(new Buffer());
        else
            return std::unique_ptr<Buffer>(new Buffer(v));
    }
    

    However, your original concatenation statements won't be compilable, then, because Proxy::operator<<(const T& v) now returns an object of type std::unique_ptr<Buffer> rather than Buffer, meaning that this returned object doesn't have its own Proxy::operator<<(const T& v) function defined and so multiple concatenations won't work without first explicitly de-referencing the returned pointer:

    const Proxy pr;
    std::unique_ptr<Buffer> pb = pr << "This ";
    //  pb << "doesn't " << "use the copy-constructor"; // This line doesn't work
    *pb << "doesn't " << "use the copy-constructor";
    

    In other words, your classes rely inherently on copying and so, if you really want to avoid copying, you should throw them away and completely re-design your logging functionalities.


    I'm sure there's some black-magic voodoo which can be invoked to make this possible --- albeit at the cost of one's sanity.