Does std::string move constructor actually move?












21















So here i got a small test program:



#include <string>
#include <iostream>
#include <memory>
#include <vector>

class Test
{
public:
Test(const std::vector<int>& a_, const std::string& b_)
: a(std::move(a_)),
b(std::move(b_)),
vBufAddr(reinterpret_cast<long long>(a.data())),
sBufAddr(reinterpret_cast<long long>(b.data()))
{}

Test(Test&& mv)
: a(std::move(mv.a)),
b(std::move(mv.b)),
vBufAddr(reinterpret_cast<long long>(a.data())),
sBufAddr(reinterpret_cast<long long>(b.data()))
{}

bool operator==(const Test& cmp)
{
if (vBufAddr != cmp.vBufAddr) {
std::cout << "Vector buffers differ: " << std::endl
<< "Ours: " << std::hex << vBufAddr << std::endl
<< "Theirs: " << cmp.vBufAddr << std::endl;
return false;
}

if (sBufAddr != cmp.sBufAddr) {
std::cout << "String buffers differ: " << std::endl
<< "Ours: " << std::hex << sBufAddr << std::endl
<< "Theirs: " << cmp.sBufAddr << std::endl;
return false;
}
}

private:

std::vector<int> a;
std::string b;
long long vBufAddr;
long long sBufAddr;
};

int main()
{
Test obj1 { {0x01, 0x02, 0x03, 0x04}, {0x01, 0x02, 0x03, 0x04}};
Test obj2(std::move(obj1));

obj1 == obj2;


return 0;
}


Software i used for test:




Compiler: gcc 7.3.0



Compiler flags: -std=c++11



OS: Linux Mint 19 (tara) with upstream release Ubuntu 18.04 LTS (bionic)




The results i see here, that after move, vector buffer still has the same address, but string buffer doesn't. So it looks to me, that it allocated fresh one, instead of just swapping buffer pointers. What causes such behavior?










share|improve this question





























    21















    So here i got a small test program:



    #include <string>
    #include <iostream>
    #include <memory>
    #include <vector>

    class Test
    {
    public:
    Test(const std::vector<int>& a_, const std::string& b_)
    : a(std::move(a_)),
    b(std::move(b_)),
    vBufAddr(reinterpret_cast<long long>(a.data())),
    sBufAddr(reinterpret_cast<long long>(b.data()))
    {}

    Test(Test&& mv)
    : a(std::move(mv.a)),
    b(std::move(mv.b)),
    vBufAddr(reinterpret_cast<long long>(a.data())),
    sBufAddr(reinterpret_cast<long long>(b.data()))
    {}

    bool operator==(const Test& cmp)
    {
    if (vBufAddr != cmp.vBufAddr) {
    std::cout << "Vector buffers differ: " << std::endl
    << "Ours: " << std::hex << vBufAddr << std::endl
    << "Theirs: " << cmp.vBufAddr << std::endl;
    return false;
    }

    if (sBufAddr != cmp.sBufAddr) {
    std::cout << "String buffers differ: " << std::endl
    << "Ours: " << std::hex << sBufAddr << std::endl
    << "Theirs: " << cmp.sBufAddr << std::endl;
    return false;
    }
    }

    private:

    std::vector<int> a;
    std::string b;
    long long vBufAddr;
    long long sBufAddr;
    };

    int main()
    {
    Test obj1 { {0x01, 0x02, 0x03, 0x04}, {0x01, 0x02, 0x03, 0x04}};
    Test obj2(std::move(obj1));

    obj1 == obj2;


    return 0;
    }


    Software i used for test:




    Compiler: gcc 7.3.0



    Compiler flags: -std=c++11



    OS: Linux Mint 19 (tara) with upstream release Ubuntu 18.04 LTS (bionic)




    The results i see here, that after move, vector buffer still has the same address, but string buffer doesn't. So it looks to me, that it allocated fresh one, instead of just swapping buffer pointers. What causes such behavior?










    share|improve this question



























      21












      21








      21


      3






      So here i got a small test program:



      #include <string>
      #include <iostream>
      #include <memory>
      #include <vector>

      class Test
      {
      public:
      Test(const std::vector<int>& a_, const std::string& b_)
      : a(std::move(a_)),
      b(std::move(b_)),
      vBufAddr(reinterpret_cast<long long>(a.data())),
      sBufAddr(reinterpret_cast<long long>(b.data()))
      {}

      Test(Test&& mv)
      : a(std::move(mv.a)),
      b(std::move(mv.b)),
      vBufAddr(reinterpret_cast<long long>(a.data())),
      sBufAddr(reinterpret_cast<long long>(b.data()))
      {}

      bool operator==(const Test& cmp)
      {
      if (vBufAddr != cmp.vBufAddr) {
      std::cout << "Vector buffers differ: " << std::endl
      << "Ours: " << std::hex << vBufAddr << std::endl
      << "Theirs: " << cmp.vBufAddr << std::endl;
      return false;
      }

      if (sBufAddr != cmp.sBufAddr) {
      std::cout << "String buffers differ: " << std::endl
      << "Ours: " << std::hex << sBufAddr << std::endl
      << "Theirs: " << cmp.sBufAddr << std::endl;
      return false;
      }
      }

      private:

      std::vector<int> a;
      std::string b;
      long long vBufAddr;
      long long sBufAddr;
      };

      int main()
      {
      Test obj1 { {0x01, 0x02, 0x03, 0x04}, {0x01, 0x02, 0x03, 0x04}};
      Test obj2(std::move(obj1));

      obj1 == obj2;


      return 0;
      }


      Software i used for test:




      Compiler: gcc 7.3.0



      Compiler flags: -std=c++11



      OS: Linux Mint 19 (tara) with upstream release Ubuntu 18.04 LTS (bionic)




      The results i see here, that after move, vector buffer still has the same address, but string buffer doesn't. So it looks to me, that it allocated fresh one, instead of just swapping buffer pointers. What causes such behavior?










      share|improve this question
















      So here i got a small test program:



      #include <string>
      #include <iostream>
      #include <memory>
      #include <vector>

      class Test
      {
      public:
      Test(const std::vector<int>& a_, const std::string& b_)
      : a(std::move(a_)),
      b(std::move(b_)),
      vBufAddr(reinterpret_cast<long long>(a.data())),
      sBufAddr(reinterpret_cast<long long>(b.data()))
      {}

      Test(Test&& mv)
      : a(std::move(mv.a)),
      b(std::move(mv.b)),
      vBufAddr(reinterpret_cast<long long>(a.data())),
      sBufAddr(reinterpret_cast<long long>(b.data()))
      {}

      bool operator==(const Test& cmp)
      {
      if (vBufAddr != cmp.vBufAddr) {
      std::cout << "Vector buffers differ: " << std::endl
      << "Ours: " << std::hex << vBufAddr << std::endl
      << "Theirs: " << cmp.vBufAddr << std::endl;
      return false;
      }

      if (sBufAddr != cmp.sBufAddr) {
      std::cout << "String buffers differ: " << std::endl
      << "Ours: " << std::hex << sBufAddr << std::endl
      << "Theirs: " << cmp.sBufAddr << std::endl;
      return false;
      }
      }

      private:

      std::vector<int> a;
      std::string b;
      long long vBufAddr;
      long long sBufAddr;
      };

      int main()
      {
      Test obj1 { {0x01, 0x02, 0x03, 0x04}, {0x01, 0x02, 0x03, 0x04}};
      Test obj2(std::move(obj1));

      obj1 == obj2;


      return 0;
      }


      Software i used for test:




      Compiler: gcc 7.3.0



      Compiler flags: -std=c++11



      OS: Linux Mint 19 (tara) with upstream release Ubuntu 18.04 LTS (bionic)




      The results i see here, that after move, vector buffer still has the same address, but string buffer doesn't. So it looks to me, that it allocated fresh one, instead of just swapping buffer pointers. What causes such behavior?







      c++ c++11 move move-semantics stdstring






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Feb 3 at 13:01









      Yvette Colomb

      20.3k1571113




      20.3k1571113










      asked Jan 29 at 11:53









      toozyfuzzytoozyfuzzy

      433214




      433214
























          1 Answer
          1






          active

          oldest

          votes


















          36














          You're likely seeing the effects of the small/short string optimization. To avoid unnecessary allocations for every tiny little string, many implementations of std::string include a small fixed size array to hold small strings without requiring new (this array is usually repurposes some of the other members that aren't necessary when dynamic allocation has not been used, so it consumes little or no additional memory to provide it, either for small or large strings), and those strings don't benefit from std::move (but they're small, so it's fine). Larger strings will require dynamic allocation, and will transfer the pointer as you expect.



          Just for demonstration, this code on g++:





          void move_test(std::string&& s) {
          std::string s2 = std::move(s);
          std::cout << "; After move: " << std::hex << reinterpret_cast<uintptr_t>(s2.data()) << std::endl;
          }

          int main()
          {
          std::string sbase;

          for (size_t len=0; len < 32; ++len) {
          std::string s1 = sbase;
          std::cout << "Length " << len << " - Before move: " << std::hex << reinterpret_cast<uintptr_t>(s1.data());
          move_test(std::move(s1));
          sbase += 'a';
          }
          }


          Try it online!



          produces high (stack) addresses that change on move construction for lengths of 15 or less (presumably varies with architecture pointer size), but switches to low (heap) addresses that remain unchanged after move construction once you hit length 16 or higher (the switch is at 16, not 17, because it is NUL-terminating the strings, since C++11 and higher require it).



          To be 100% clear: This is an implementation detail. No part of the C++ spec requires this behavior, so you should not rely on it occurring at all, and when it occurs, you should not rely on it occurring for specific string lengths.






          share|improve this answer





















          • 4





            "include a small fixed size array to hold small strings" - You don't usually include an array for SSO but you reuse the available storage (size / pointer / ...) and add a flag to indicate if you have a short string.

            – Holt
            Jan 29 at 12:05






          • 2





            @Holt: Sure, but the effect is the same for the OP's purposes. For that matter, a dedicated flag isn't necessary if you make the cutoff a strict length/capacity limit (which, if you're just shoving data into pointers members, is going to be limited anyway).

            – ShadowRanger
            Jan 29 at 12:23








          • 4





            @Holt: Which is to say, you use a discriminated union that contains a char[N] for short strings. So the array is there in the type, even though it might not be present in all objects.

            – MSalters
            Jan 29 at 12:26






          • 1





            @Holt: I did qualify the statement, just to be clear.

            – ShadowRanger
            Jan 29 at 12:40






          • 2





            @Ruslan: Looks like it. I just checked GCC 8's header, and it just defines a enum constant _S_local_capacity = 15 / sizeof(_CharT), then defines union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; So it's actually reserving a fixed 16 bytes unioned with the capacity as a size_type, which means the SSO array is 8-12 bytes larger than the members it's sharing. The 32 bit string is smaller (sizeof reports 24 bytes, vs. 32 for the 64 bit string), but I'm guessing it could have been 12 for 32 bit and 24 for 64 bit without the SSO.

            – ShadowRanger
            Jan 29 at 15:16














          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54420470%2fdoes-stdstring-move-constructor-actually-move%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          36














          You're likely seeing the effects of the small/short string optimization. To avoid unnecessary allocations for every tiny little string, many implementations of std::string include a small fixed size array to hold small strings without requiring new (this array is usually repurposes some of the other members that aren't necessary when dynamic allocation has not been used, so it consumes little or no additional memory to provide it, either for small or large strings), and those strings don't benefit from std::move (but they're small, so it's fine). Larger strings will require dynamic allocation, and will transfer the pointer as you expect.



          Just for demonstration, this code on g++:





          void move_test(std::string&& s) {
          std::string s2 = std::move(s);
          std::cout << "; After move: " << std::hex << reinterpret_cast<uintptr_t>(s2.data()) << std::endl;
          }

          int main()
          {
          std::string sbase;

          for (size_t len=0; len < 32; ++len) {
          std::string s1 = sbase;
          std::cout << "Length " << len << " - Before move: " << std::hex << reinterpret_cast<uintptr_t>(s1.data());
          move_test(std::move(s1));
          sbase += 'a';
          }
          }


          Try it online!



          produces high (stack) addresses that change on move construction for lengths of 15 or less (presumably varies with architecture pointer size), but switches to low (heap) addresses that remain unchanged after move construction once you hit length 16 or higher (the switch is at 16, not 17, because it is NUL-terminating the strings, since C++11 and higher require it).



          To be 100% clear: This is an implementation detail. No part of the C++ spec requires this behavior, so you should not rely on it occurring at all, and when it occurs, you should not rely on it occurring for specific string lengths.






          share|improve this answer





















          • 4





            "include a small fixed size array to hold small strings" - You don't usually include an array for SSO but you reuse the available storage (size / pointer / ...) and add a flag to indicate if you have a short string.

            – Holt
            Jan 29 at 12:05






          • 2





            @Holt: Sure, but the effect is the same for the OP's purposes. For that matter, a dedicated flag isn't necessary if you make the cutoff a strict length/capacity limit (which, if you're just shoving data into pointers members, is going to be limited anyway).

            – ShadowRanger
            Jan 29 at 12:23








          • 4





            @Holt: Which is to say, you use a discriminated union that contains a char[N] for short strings. So the array is there in the type, even though it might not be present in all objects.

            – MSalters
            Jan 29 at 12:26






          • 1





            @Holt: I did qualify the statement, just to be clear.

            – ShadowRanger
            Jan 29 at 12:40






          • 2





            @Ruslan: Looks like it. I just checked GCC 8's header, and it just defines a enum constant _S_local_capacity = 15 / sizeof(_CharT), then defines union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; So it's actually reserving a fixed 16 bytes unioned with the capacity as a size_type, which means the SSO array is 8-12 bytes larger than the members it's sharing. The 32 bit string is smaller (sizeof reports 24 bytes, vs. 32 for the 64 bit string), but I'm guessing it could have been 12 for 32 bit and 24 for 64 bit without the SSO.

            – ShadowRanger
            Jan 29 at 15:16


















          36














          You're likely seeing the effects of the small/short string optimization. To avoid unnecessary allocations for every tiny little string, many implementations of std::string include a small fixed size array to hold small strings without requiring new (this array is usually repurposes some of the other members that aren't necessary when dynamic allocation has not been used, so it consumes little or no additional memory to provide it, either for small or large strings), and those strings don't benefit from std::move (but they're small, so it's fine). Larger strings will require dynamic allocation, and will transfer the pointer as you expect.



          Just for demonstration, this code on g++:





          void move_test(std::string&& s) {
          std::string s2 = std::move(s);
          std::cout << "; After move: " << std::hex << reinterpret_cast<uintptr_t>(s2.data()) << std::endl;
          }

          int main()
          {
          std::string sbase;

          for (size_t len=0; len < 32; ++len) {
          std::string s1 = sbase;
          std::cout << "Length " << len << " - Before move: " << std::hex << reinterpret_cast<uintptr_t>(s1.data());
          move_test(std::move(s1));
          sbase += 'a';
          }
          }


          Try it online!



          produces high (stack) addresses that change on move construction for lengths of 15 or less (presumably varies with architecture pointer size), but switches to low (heap) addresses that remain unchanged after move construction once you hit length 16 or higher (the switch is at 16, not 17, because it is NUL-terminating the strings, since C++11 and higher require it).



          To be 100% clear: This is an implementation detail. No part of the C++ spec requires this behavior, so you should not rely on it occurring at all, and when it occurs, you should not rely on it occurring for specific string lengths.






          share|improve this answer





















          • 4





            "include a small fixed size array to hold small strings" - You don't usually include an array for SSO but you reuse the available storage (size / pointer / ...) and add a flag to indicate if you have a short string.

            – Holt
            Jan 29 at 12:05






          • 2





            @Holt: Sure, but the effect is the same for the OP's purposes. For that matter, a dedicated flag isn't necessary if you make the cutoff a strict length/capacity limit (which, if you're just shoving data into pointers members, is going to be limited anyway).

            – ShadowRanger
            Jan 29 at 12:23








          • 4





            @Holt: Which is to say, you use a discriminated union that contains a char[N] for short strings. So the array is there in the type, even though it might not be present in all objects.

            – MSalters
            Jan 29 at 12:26






          • 1





            @Holt: I did qualify the statement, just to be clear.

            – ShadowRanger
            Jan 29 at 12:40






          • 2





            @Ruslan: Looks like it. I just checked GCC 8's header, and it just defines a enum constant _S_local_capacity = 15 / sizeof(_CharT), then defines union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; So it's actually reserving a fixed 16 bytes unioned with the capacity as a size_type, which means the SSO array is 8-12 bytes larger than the members it's sharing. The 32 bit string is smaller (sizeof reports 24 bytes, vs. 32 for the 64 bit string), but I'm guessing it could have been 12 for 32 bit and 24 for 64 bit without the SSO.

            – ShadowRanger
            Jan 29 at 15:16
















          36












          36








          36







          You're likely seeing the effects of the small/short string optimization. To avoid unnecessary allocations for every tiny little string, many implementations of std::string include a small fixed size array to hold small strings without requiring new (this array is usually repurposes some of the other members that aren't necessary when dynamic allocation has not been used, so it consumes little or no additional memory to provide it, either for small or large strings), and those strings don't benefit from std::move (but they're small, so it's fine). Larger strings will require dynamic allocation, and will transfer the pointer as you expect.



          Just for demonstration, this code on g++:





          void move_test(std::string&& s) {
          std::string s2 = std::move(s);
          std::cout << "; After move: " << std::hex << reinterpret_cast<uintptr_t>(s2.data()) << std::endl;
          }

          int main()
          {
          std::string sbase;

          for (size_t len=0; len < 32; ++len) {
          std::string s1 = sbase;
          std::cout << "Length " << len << " - Before move: " << std::hex << reinterpret_cast<uintptr_t>(s1.data());
          move_test(std::move(s1));
          sbase += 'a';
          }
          }


          Try it online!



          produces high (stack) addresses that change on move construction for lengths of 15 or less (presumably varies with architecture pointer size), but switches to low (heap) addresses that remain unchanged after move construction once you hit length 16 or higher (the switch is at 16, not 17, because it is NUL-terminating the strings, since C++11 and higher require it).



          To be 100% clear: This is an implementation detail. No part of the C++ spec requires this behavior, so you should not rely on it occurring at all, and when it occurs, you should not rely on it occurring for specific string lengths.






          share|improve this answer















          You're likely seeing the effects of the small/short string optimization. To avoid unnecessary allocations for every tiny little string, many implementations of std::string include a small fixed size array to hold small strings without requiring new (this array is usually repurposes some of the other members that aren't necessary when dynamic allocation has not been used, so it consumes little or no additional memory to provide it, either for small or large strings), and those strings don't benefit from std::move (but they're small, so it's fine). Larger strings will require dynamic allocation, and will transfer the pointer as you expect.



          Just for demonstration, this code on g++:





          void move_test(std::string&& s) {
          std::string s2 = std::move(s);
          std::cout << "; After move: " << std::hex << reinterpret_cast<uintptr_t>(s2.data()) << std::endl;
          }

          int main()
          {
          std::string sbase;

          for (size_t len=0; len < 32; ++len) {
          std::string s1 = sbase;
          std::cout << "Length " << len << " - Before move: " << std::hex << reinterpret_cast<uintptr_t>(s1.data());
          move_test(std::move(s1));
          sbase += 'a';
          }
          }


          Try it online!



          produces high (stack) addresses that change on move construction for lengths of 15 or less (presumably varies with architecture pointer size), but switches to low (heap) addresses that remain unchanged after move construction once you hit length 16 or higher (the switch is at 16, not 17, because it is NUL-terminating the strings, since C++11 and higher require it).



          To be 100% clear: This is an implementation detail. No part of the C++ spec requires this behavior, so you should not rely on it occurring at all, and when it occurs, you should not rely on it occurring for specific string lengths.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Jan 29 at 15:18

























          answered Jan 29 at 11:57









          ShadowRangerShadowRanger

          63.3k660100




          63.3k660100








          • 4





            "include a small fixed size array to hold small strings" - You don't usually include an array for SSO but you reuse the available storage (size / pointer / ...) and add a flag to indicate if you have a short string.

            – Holt
            Jan 29 at 12:05






          • 2





            @Holt: Sure, but the effect is the same for the OP's purposes. For that matter, a dedicated flag isn't necessary if you make the cutoff a strict length/capacity limit (which, if you're just shoving data into pointers members, is going to be limited anyway).

            – ShadowRanger
            Jan 29 at 12:23








          • 4





            @Holt: Which is to say, you use a discriminated union that contains a char[N] for short strings. So the array is there in the type, even though it might not be present in all objects.

            – MSalters
            Jan 29 at 12:26






          • 1





            @Holt: I did qualify the statement, just to be clear.

            – ShadowRanger
            Jan 29 at 12:40






          • 2





            @Ruslan: Looks like it. I just checked GCC 8's header, and it just defines a enum constant _S_local_capacity = 15 / sizeof(_CharT), then defines union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; So it's actually reserving a fixed 16 bytes unioned with the capacity as a size_type, which means the SSO array is 8-12 bytes larger than the members it's sharing. The 32 bit string is smaller (sizeof reports 24 bytes, vs. 32 for the 64 bit string), but I'm guessing it could have been 12 for 32 bit and 24 for 64 bit without the SSO.

            – ShadowRanger
            Jan 29 at 15:16
















          • 4





            "include a small fixed size array to hold small strings" - You don't usually include an array for SSO but you reuse the available storage (size / pointer / ...) and add a flag to indicate if you have a short string.

            – Holt
            Jan 29 at 12:05






          • 2





            @Holt: Sure, but the effect is the same for the OP's purposes. For that matter, a dedicated flag isn't necessary if you make the cutoff a strict length/capacity limit (which, if you're just shoving data into pointers members, is going to be limited anyway).

            – ShadowRanger
            Jan 29 at 12:23








          • 4





            @Holt: Which is to say, you use a discriminated union that contains a char[N] for short strings. So the array is there in the type, even though it might not be present in all objects.

            – MSalters
            Jan 29 at 12:26






          • 1





            @Holt: I did qualify the statement, just to be clear.

            – ShadowRanger
            Jan 29 at 12:40






          • 2





            @Ruslan: Looks like it. I just checked GCC 8's header, and it just defines a enum constant _S_local_capacity = 15 / sizeof(_CharT), then defines union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; So it's actually reserving a fixed 16 bytes unioned with the capacity as a size_type, which means the SSO array is 8-12 bytes larger than the members it's sharing. The 32 bit string is smaller (sizeof reports 24 bytes, vs. 32 for the 64 bit string), but I'm guessing it could have been 12 for 32 bit and 24 for 64 bit without the SSO.

            – ShadowRanger
            Jan 29 at 15:16










          4




          4





          "include a small fixed size array to hold small strings" - You don't usually include an array for SSO but you reuse the available storage (size / pointer / ...) and add a flag to indicate if you have a short string.

          – Holt
          Jan 29 at 12:05





          "include a small fixed size array to hold small strings" - You don't usually include an array for SSO but you reuse the available storage (size / pointer / ...) and add a flag to indicate if you have a short string.

          – Holt
          Jan 29 at 12:05




          2




          2





          @Holt: Sure, but the effect is the same for the OP's purposes. For that matter, a dedicated flag isn't necessary if you make the cutoff a strict length/capacity limit (which, if you're just shoving data into pointers members, is going to be limited anyway).

          – ShadowRanger
          Jan 29 at 12:23







          @Holt: Sure, but the effect is the same for the OP's purposes. For that matter, a dedicated flag isn't necessary if you make the cutoff a strict length/capacity limit (which, if you're just shoving data into pointers members, is going to be limited anyway).

          – ShadowRanger
          Jan 29 at 12:23






          4




          4





          @Holt: Which is to say, you use a discriminated union that contains a char[N] for short strings. So the array is there in the type, even though it might not be present in all objects.

          – MSalters
          Jan 29 at 12:26





          @Holt: Which is to say, you use a discriminated union that contains a char[N] for short strings. So the array is there in the type, even though it might not be present in all objects.

          – MSalters
          Jan 29 at 12:26




          1




          1





          @Holt: I did qualify the statement, just to be clear.

          – ShadowRanger
          Jan 29 at 12:40





          @Holt: I did qualify the statement, just to be clear.

          – ShadowRanger
          Jan 29 at 12:40




          2




          2





          @Ruslan: Looks like it. I just checked GCC 8's header, and it just defines a enum constant _S_local_capacity = 15 / sizeof(_CharT), then defines union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; So it's actually reserving a fixed 16 bytes unioned with the capacity as a size_type, which means the SSO array is 8-12 bytes larger than the members it's sharing. The 32 bit string is smaller (sizeof reports 24 bytes, vs. 32 for the 64 bit string), but I'm guessing it could have been 12 for 32 bit and 24 for 64 bit without the SSO.

          – ShadowRanger
          Jan 29 at 15:16







          @Ruslan: Looks like it. I just checked GCC 8's header, and it just defines a enum constant _S_local_capacity = 15 / sizeof(_CharT), then defines union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; So it's actually reserving a fixed 16 bytes unioned with the capacity as a size_type, which means the SSO array is 8-12 bytes larger than the members it's sharing. The 32 bit string is smaller (sizeof reports 24 bytes, vs. 32 for the 64 bit string), but I'm guessing it could have been 12 for 32 bit and 24 for 64 bit without the SSO.

          – ShadowRanger
          Jan 29 at 15:16






















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54420470%2fdoes-stdstring-move-constructor-actually-move%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Can a sorcerer learn a 5th-level spell early by creating spell slots using the Font of Magic feature?

          Does disintegrating a polymorphed enemy still kill it after the 2018 errata?

          A Topological Invariant for $pi_3(U(n))$