C++ std::string寫時復制與深淺拷貝


很久以前就了解過std::string的寫時復制(copy on write)優化,但和深淺拷貝放到一起的時候,就不是那么直截了當了。

 

std::string到底是深拷貝還是淺拷貝呢?網上兩種說法都有,我的理解是:深拷貝。

// copy on write
static void TestStringCopyCase1() {
    std::string a = "Hello World";
    std::string b = a;
    printf("pointer of a: %p\n", a.c_str());
    printf("pointer of b: %p\n", b.c_str());
}

// copy on write
static void TestStringCopyCase2() {
    std::string a = "Hello World";
    std::string b = a;
    printf("pointer of a: %p\n", a.c_str());
    printf("pointer of b: %p\n", b.c_str());
    b[0] = 'h';
    // b += "!";
    printf("pointer of a: %p\n", a.c_str());
    printf("pointer of b: %p\n", b.c_str());
    std::cout << a << std::endl;
    std::cout << b << std::endl;
}

// output:
pointer of a: 0x1144028
pointer of b: 0x1144028
pointer of a: 0x1144028 pointer of b: 0x1144028 pointer of a: 0x1144028 pointer of b: 0x1144058 Hello World hello World

這兩個case很明確地證明std::string是深拷貝的,對副本的修改不會影響到原件。只不過,在修改副本之前,它們的c_str()指針是指向同一地址的,只有在嘗試寫入的時候,才會區分開來。

具體的實現方法,是對數據塊進行了引用計數,嘗試修改的時候,引用計數不為1,就要復制再修改。

那么這里就隱藏了一個空子,如果繞過引用計數,直接修改原始數據,會怎樣?

// misuse: modify b, but a is effected.
static void TestStringCopyCase3() {
    std::string a = "Hello World";
    std::string b = a;
    char* char_array = (char *)b.c_str();
    char_array[0] = 'h';
    printf("pointer of a: %p\n", a.c_str());
    printf("pointer of b: %p\n", b.c_str());
    std::cout << a << std::endl;
    std::cout << b << std::endl;
}

// output:
pointer of a: 0x1144028
pointer of b: 0x1144028
hello World
hello World

修改副本,導致原件也被修改。是一個容易引起錯誤的地方。

如何避免呢?

// deep copy to avoid misuse
static void TestStringCopyCase4() {
    std::string a = "Hello World";
    std::string b = a.c_str(); // deep copy
    char* char_array = (char *)b.c_str();
    char_array[0] = 'h';
    printf("pointer of a: %p\n", a.c_str());
    printf("pointer of b: %p\n", b.c_str());
    std::cout << a << std::endl;
    std::cout << b << std::endl;
}

// output:
pointer of a: 0x1144028
pointer of b: 0x1144058
Hello World
hello World

復制的時候,直接復制源數據,繞開寫時復制。這就給人一種錯覺,好像std::string的拷貝函數是淺拷貝,需要刻意深拷貝。

結論:

如果使用std::string本身的成員函數或者操作符來操作std::string,它本身就是深拷貝的;

如果使用指針直接操作std::string源數據,會繞過“寫時復制”機制,需要主動deep copy,以避免數據誤寫。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM