简体   繁体   中英

How do I cast `std::string` to `std::vector<unsigned char>` without making a copy?

There is a library function I want to call whose signature is:

bool WriteBinary(const std::vector<uint8_t> & DataToWrite);

I have an std::string variable, I want to send it this function as argument.

void WriteString(const std::string & TextData)
{
    // ...
    WriteBinary(TextData);  // <-- How to make this line work?
    // ...
}

Is there a way I can directly send the std::string variable without making a copy of it?

There's no way to do this, because the layouts of the two types are not guaranteed to be similar in any way.

The best approach is to fix WriteBinary to use "iterators" instead:

bool WriteBinary(const unsigned char* first, const unsigned char* last);

Or template it:

template <typename Iter>
bool WriteBinary(Iter first, Iter last);

And then you can use it for strings, vectors, or any other container you like!

Otherwise you can use the iterator constructor to do the copy as efficiently as possible:

WriteBinary(std::vector<uint8_t>(TextData.begin(), TextData.end()));

I am afraid that is not possible. Neither string nor vector has a constructor which would allow it to "adopt" a pre-existing buffer. And it's very likely the memory layout of a string and a vector differ, so there is no casting possible.

Is there a way I can directly send the std::string variable without making a copy of it?

No, certainly not safely.

If you have control over the library, I'd suggest a slight refactor.

Either add:

template<typename CharT>
void WriteBinary(CharT const* buffer, size_t count);

Or:

template<typename FwdIter>
void WriteBinary(FwdIter begin, FwdIter end);

And then make your existing WriteBinary and WriteString call it:

void WriteBinary(std::vector<uint8_t> const& vec)
{ WriteBinary(&*vec.begin(), vec.size()); }

void WriteString(std::string const& s)
{ WriteBinary(&*s.begin(), s.size()); }

Or:

void WriteBinary(std::vector<uint8_t> const& vec)
{ WriteBinary(vec.begin(), vec.end()); }

void WriteString(std::string const& s)
{ WriteBinary(s.begin(), s.end()); }

Personally, I'd prefer the iterator based approach. It feels "cleaner".

(Note: for the pointer/size approach, you'd probably want to check for empty. Some implementations may assert if you deference the result of begin() on an empty vector/string.)

You are of course free to hack yourself through to obtain the desired result. But you should prepare for a summary execution by co-workers or people wanting to use/maintain/modify or port your code...

... don't do it!...

int main (void)
{
  std::string teststring("HERE IS A TEST");
  char * p = (char*)malloc(3*sizeof(char*) + sizeof(std::allocator<char>));
  std::allocator< std::allocator<char> > alloc_alloc;
  char * pa = p + 3*sizeof(char*);
  alloc_alloc.construct((std::allocator<char>*)pa);

  char const * cp = teststring.data();
  char const * * vec_it = (char const**)p;

  vec_it[0] = cp;
  vec_it[1] = cp + teststring.length();
  vec_it[2] = vec_it[1];

  std::vector<char> &a_vector = *((std::vector<char, std::allocator<char>>*)p);

  cout << a_vector.size() << " elements in vector!" << endl;

  for (std::vector<char>::iterator i=a_vector.begin(); i!=a_vector.end(); ++i) 
  {
    cout << *i;
  }
  cout << endl;
  // set char 8 to 66 = B
  a_vector[8] = 66;

  cout << teststring << endl;

}

Prints

14 elements in vector!
HERE IS A TEST
HERE IS B TEST

Note:

As correctly mentioned in the comments, this solution relys on the vector to have the following data layout and requires the string to store it's data contiguously (dunno whether this is predetermined by the standard).

template <typename T>
class vector
{
  T* _first, _last, _end;
  std::allocator<T> _alloc;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM