1. 程式人生 > 實用技巧 >C++ std::string寫時複製與深淺拷貝

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,以避免資料誤寫。