Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I take ownership of a C++ std::string char data without copying and keeping std::string object?

How can I take ownership of std::string char data without copying and withoug keeping source std::string object? (I want to use moving semantics but between different types.)

I use the C++11 Clang compiler and Boost.

Basically I want to do something equivalent to this:

{
    std::string s(“Possibly very long user string”);
    const char* mine = s.c_str();

    // 'mine' will be passed along,
    pass(mine);

    //Made-up call
    s.release_data();

    // 's' should not release data, but it should properly destroy itself otherwise.
}

To clarify, I do need to get rid of std::string: further down the road. The code deals with both string and binary data and should handle it in the same format. And I do want the data from std::string, because that comes from another code layer that works with std::string.

To give more perspective where I run into wanting to do so: for example I have an asynchronous socket wrapper that should be able to take both std::string and binary data from user for writing. Both "API" write versions (taking std::string or row binary data) internally resolve to the same (binary) write. I need to avoid any copying as the string may be long.

WriteId     write( std::unique_ptr< std::string > strToWrite )
{

    // Convert std::string data to contiguous byte storage
    // that will be further passed along to other
    // functions (also with the moving semantics).
    // strToWrite.c_str() would be a solution to my problem
    // if I could tell strToWrite to simply give up its
    // ownership. Is there a way?

    unique_ptr<std::vector<char> > dataToWrite= ??

    //
    scheduleWrite( dataToWrite );
}

void scheduledWrite( std::unique_ptr< std::vecor<char> > data)
{
    …
}

std::unique_ptr in this example to illustrate ownership transfer: any other approach with the same semantics is fine to me.

I am wondering about solutions to this specific case (with std::string char buffer) and this sort of problem with strings, streams and similar general: tips to approach moving buffers around between string, stream, std containers and buffer types.

I would also appreciated tips and links with C++ design approaches and specific techniques when it comes to passing buffer data around between different API's/types without copying. I mention but not using streams because I'm shaky on that subject.

like image 318
minsk Avatar asked Jul 02 '12 21:07

minsk


2 Answers

How can I take ownership of std::string char data without copying and withoug keeping source std::string object ? (I want to use moving semantics but between different types)

You cannot do this safely.

For a specific implementation, and in some circumstances, you could do something awful like use aliasing to modify private member variables inside the string to trick the string into thinking it no longer owns a buffer. But even if you're willing to try this it won't always work. E.g. consider the small string optimization where a string does not have a pointer to some external buffer holding the data, the data is inside the string object itself.


If you want to avoid copying you could consider changing the interface to scheduledWrite. One possibility is something like:

template<typename Container>
void scheduledWrite(Container data)
{
    // requires data[i], data.size(), and &data[n] == &data[0] + n for n [0,size)
    …
}

// move resources from object owned by a unique_ptr
WriteId write( std::unique_ptr< std::vector<char> > vecToWrite)
{
    scheduleWrite(std::move(*vecToWrite));
}

WriteId write( std::unique_ptr< std::string > strToWrite)
{
    scheduleWrite(std::move(*strToWrite));
}

// move resources from object passed by value (callers also have to take care to avoid copies)
WriteId write(std::string strToWrite)
{
    scheduleWrite(std::move(strToWrite));
}

// assume ownership of raw pointer
// requires data to have been allocated with new char[]
WriteId write(char const *data,size_t size) // you could also accept an allocator or deallocation function and make ptr_adapter deal with it
{
    struct ptr_adapter {
        std::unique_ptr<char const []> ptr;
        size_t m_size;
        char const &operator[] (size_t i) { return ptr[i]; }
        size_t size() { return m_size; }
    };

    scheduleWrite(ptr_adapter{data,size});
}
like image 171
bames53 Avatar answered Sep 19 '22 15:09

bames53


This class take ownership of a string using move semantics and shared_ptr:

struct charbuffer
{
  charbuffer()
  {}

  charbuffer(size_t n, char c)
  : _data(std::make_shared<std::string>(n, c))
  {}

  explicit charbuffer(std::string&& str)
  : _data(std::make_shared<std::string>(str))
  {}

  charbuffer(const charbuffer& other)
  : _data(other._data)
  {}

  charbuffer(charbuffer&& other)
  {
    swap(other);
  }

  charbuffer& operator=(charbuffer other)
  {
    swap(other);
    return *this;
  }

  void swap(charbuffer& other)
  {
    using std::swap;
    swap(_data, other._data);
  }

  char& operator[](int i)
  { 
    return (*_data)[i];
  } 

  char operator[](int i) const
  { 
    return (*_data)[i];
  } 

  size_t size() const
  {
    return _data->size();
  }

  bool valid() const
  { 
    return _data;
  }

private:
  std::shared_ptr<std::string> _data;

};

Example usage:

std::string s("possibly very long user string");

charbuffer cb(std::move(s)); // s is empty now

// use charbuffer...
like image 45
Gigi Avatar answered Sep 21 '22 15:09

Gigi