2015-04-15 48 views
1

我有一個構造新對象的工廠類。 新對象應該不被複制,但可能被移動。所以我想我會刪除複製構造函數和複製賦值運算符,同時提供移動構造函數和移動賦值運算符。指定右值作爲返回值時出現分段錯誤

我認爲,爲了幫助傳達對象必須移動到而不是複製的想法,我會返回一個右值引用。但是,在這樣做的時候,編譯器似乎總是生成代碼,這些代碼會破壞返回的「到期」對象,然後向移動構造函數或移動賦值運算符提供同一個(銷燬!)對象。

所以我想知道;我有違規的標準嗎?如果是這樣,是否有警告(錯誤,最好是)我可以阻止我繼續做這麼愚蠢的事情?否則,如果我沒有違反任何標準,那麼我認爲這是一個編譯器錯誤?

// g++ (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2 
// g++ test.cpp -std=c++11 

#include <iostream> 
#include <memory> 

struct PTR { 
    char * blah = nullptr; 

    PTR(char* blah) : blah(blah) 
    { 
     std::cout << "\tctor @" << (void*)this << std::endl; 
    } 

    PTR(const PTR & copy_ctor) : blah(new char) 
    { 
     *blah = *copy_ctor.blah; 
     std::cout << "\tcopy @@" << (void*)this << "\t<-" << (void*)&copy_ctor << std::endl; 
    } 

    PTR(PTR && move_ctor) : blah(move_ctor.blah) 
    { 
     move_ctor.blah = nullptr; 
     std::cout << "\tctor&&@" << (void*)this << "\t<@" << (void*)&move_ctor << std::endl; 
    } 

    PTR & operator=(const PTR & copy_assign) 
    { delete blah; 
     blah = new char; 
     *blah = *copy_assign.blah; 
     std::cout << "[email protected]" << (void*)this << "\t<-" << (void*)&copy_assign << std::endl; 
     return *this; 
    } 

    PTR & operator=(PTR && move_assign) 
    { 
     delete blah; 
     blah = move_assign.blah; 
     move_assign.blah = nullptr; 
     std::cout << "\tmove&&@" << (void*)this << "\t<@" << (void*)&move_assign << std::endl; 
     return *this; 
    } 

    ~PTR() 
    { 
     std::cout << "\[email protected]" << (void*)this << std::endl; 
     delete blah; 
    } 
}; 

PTR make_ptr_l() { 
    PTR ptr(new char()); 
    return std::move(ptr); // Without std::move, compiler *may* opt to copy the class, which is undesired 
} 

PTR && make_ptr_r() { 
    PTR ptr(new char()); 
    return std::move(ptr); // Requires std::move to turn ptr into rvalue, otherwise compiler error 
} 

int main() { 
    std::cout << "lvalue: \n" << std::flush; 
    PTR ptr = make_ptr_l(); 
    std::cout << "successful\nrvalue new: \n" << std::flush; 
    { 
     PTR ptr_r = make_ptr_r(); 
     std::cout << "successful\nrvalue assign: \n" << std::flush; 
    } 
    ptr = make_ptr_r(); 
    std::cout << "successful" << std::endl; 
    return 0; 
} 

與上面的代碼中,可以看到以下輸出:

lvalue: 
    ctor @0x7ffed71b7a00 
    ctor&&@0x7ffed71b7a30 <@0x7ffed71b7a00 
    [email protected] 
successful 
rvalue new: 
    ctor @0x7ffed71b7a00 
    [email protected] 
    ctor&&@0x7ffed71b7a40 <@0x7ffed71b7a00 
successful 
rvalue assign: 
    [email protected] 
*** Error in `./a.out': double free or corruption (fasttop): 0x0000000001d1bc40 *** 
Aborted (core dumped) 

正如你可以rvalue new後看到,一個對象被構造,然後立即銷燬,則銷燬對象被傳遞給一個移動構造函數。因爲移動構造函數因此訪問被破壞的變量,所以這是分段錯誤的根源。

我已經用g++ (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2以及Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)試過了。

+2

'make_ptr_r'返回對局部變量的引用。當函數退出時變量消失,並且參考變得懸空。任何嘗試實際使用此參考文獻都會顯示未定義的行爲。這是一個右值引用的事實不會改變任何東西。 –

+1

要添加到@IgorTandetnik所說的內容中:'make_ptr_r'的返回類型應該是'PTR',而不是'&&'。檢查你的代碼的輸出,看看它對你有意義。 –

+0

@DanielFrey我沒有檢查輸出,甚至提到它沒有太大意義。我誤解了std :: move()和返回類型的右值引用應該將對象移出本地的想法。 – inetknght

回答

2

您正在返回臨時引用。你可以從你的輸出告訴本:

rvalue new: 
    ctor @0x7ffed71b7a00 
    [email protected] 
    ctor&&@0x7ffed71b7a40 <@0x7ffed71b7a00 

您構建和你移動構建7a40它之前摧毀7a00。事實上,你碰巧返回一個右值引用而不是左值引用並不重要。它基本上仍是形式的東西:

T& foo() { 
    T object; 
    return object; 
} 

這就是爲什麼make_ptr_l作品 - 你返回一個值,而不是引用。而且std::move()沒有必要。

+0

因此,在這種情況下,右值引用明確地不會*移動對象,因爲我已經理解它了。 – inetknght

+1

@inetknght'std :: move()'不會**移動**。這只是一個演員。 – Barry