QingYun
QingYun

Reputation: 869

Move a string in vs2012

std::string t1("aaa");
const char *p = t1.c_str();
std::string t2(std::move(t1));
if (p == t2.c_str()) {
    std::cout << "ok!" << std::endl;    
}

This code printed nothing in vs2012. It just use memmove internally to copy the string in t1 to t2. Why?

Upvotes: 2

Views: 319

Answers (3)

Sean Middleditch
Sean Middleditch

Reputation: 2615

Specifically what's going on here is "small string optimization".

Visual C++'s STL (and many others) have "fat" std::string classes that can hold a small string internally with zero memory allocations required. This is an optimization for some common use cases.

Even with std::move, since your string is small enough that it is store entirely inside the std::string itself and so must be copied. If you tried with a larger string your code would probably work, though there's absolutely no guarantee that it will and you should not depend on that behavior.

Essentially think of a std::string in these implementations as being similar-ish to:

class string {
 // space for small strings; only used if _external is nullptr
 char  _local[16];
 // pointer to heap-allocated memory; if nullptr, the string is stored in _local
 char* _external;

 // stores a string using small-string optimization is appropriate
 void store(char* src) {
   if (std::strlen(src) < sizeof(_local)) {
     // small string, store in our local buffer and set _external to nullptr
     std::strcpy(_local, src);
     delete _external;
     _external = nullptr;
   } else if (src != _external) {
     // large string, allocate space in heap
     delete _external;
     _external = new char[std::strlen(src) + 1];
     std::strcpy(_external, src);
   }
 }

public:
 // assign a C string to this string
 string(char* src) : _external(nullptr) { store(src); }
 string& operator=(char* src) { store(src); return *this; }

 // be sure to properly use either _local or _external pointer
 char* c_str() { return _external ? _external : _local; }
 bool empty() const { return c_str()[0] == 0; }
};

Upvotes: 1

James Kanze
James Kanze

Reputation: 154007

Formally, as jrok has pointed out, what you are doing is undefined behavior, so you have no right to complain, no matter what the implementation does. Practically, it will depend on the implementation. The standard doesn't require the move constructors of std::string to do anything with regards to the source string. If the implementation uses the small string optimization (as does VC++, for example), and the string is small enough to qualify, then there's no way it can do anything but memcpy the characters; they are in a char[] in the string object itself. A CoW implementation (like that in g++) might not do anything different that what it does in the copy constructor either, on the grounds that it's not worth the bother; you won't really save much. (But it might, since just swapping the pointers would save a little.)

I just tried the same thing in VC++, but with a string of 100 characters. It did the swap (and the two pointers were equal). So the behavior of VC++ depends on whether the string is small enough for the SSO to apply. G++ swaps in all cases.

Upvotes: 9

billz
billz

Reputation: 45450

Visualtudio compares the length of the string before it really moves the pointer, by debugging into std::move code, it shows it copies the string of the length is under 16 bytes

void _Assign_rv(_Myt&& _Right)
{   
   // assign by moving _Right
   if (_Right._Myres < this->_BUF_SIZE)  //_BUF_SIZE is 16, comment mine
       _Traits::move(this->_Bx._Buf, _Right._Bx._Buf,
       _Right._Mysize + 1);
   else
   {    // copy pointer
    this->_Getal().construct(&this->_Bx._Ptr, _Right._Bx._Ptr);
    _Right._Bx._Ptr = pointer();
    }
    this->_Mysize = _Right._Mysize;
    this->_Myres = _Right._Myres;
    _Right._Tidy();
}

Upvotes: 3

Related Questions