簡體   English   中英

RValue引用,指針和副本構造函數

[英]RValue references, pointers, and copy constructors

考慮以下代碼:

int three() {
    return 3;
}

template <typename T>
class Foo {
private:
    T* ptr;

public:
    void bar(T& t) { ptr = new T(t); }
    void bar(const T& t) { ptr = new T(t); }
    void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
};

int main() {
    Foo<int> foo;

    int a = 3;
    const int b = 3;

    foo.bar(a); // <--- Calls Foo::bar(T& t)
    foo.bar(b); // <--- Calls Foo::bar(const T& t)
    foo.bar(three()); // <--- Calls Foo::bar(T&& t); Runs fine, but only if either of the other two are called first!

    return 0;
}

我的問題是,為什么第三個重載Foo::bar(T&& t)會使程序崩潰? 這里到底發生了什么? 函數返回后參數t是否會被破壞?

此外,我們假設模板參數T是一個非常大的對象,並且具有非常昂貴的復制構造函數。 有什么方法可以使用RValue引用將其分配給Foo::ptr而不直接訪問此指針並進行復制嗎?

在這條線
void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
您可以取消引用未初始化的指針。 這是未定義的行為。 您必須先調用其他兩個版本的bar之一,因為您需要為對象創建內存。
所以我會做ptr = new T(std::move(t));
如果您的T類型支持移動,則將調用move構造函數。

更新資料

我建議這樣。 不知道是否需要在foo中使用指針類型:

template <typename T>
class Foo {
private:
    T obj;

public:
    void bar(T& t) { obj = t; } // assignment
    void bar(const T& t) { obj = t; } // assignment
    void bar(T&& t) { obj = std::move(t); } // move assign
};

這樣可以避免內存泄漏,這對於您的方法也很容易。
如果您確實需要類foo中的指針,該怎么做:

template <typename T>
class Foo {
private:
    T* ptr;

public:
    Foo():ptr(nullptr){}
    ~Foo(){delete ptr;}
    void bar(T& t) { 
        if(ptr)
            (*ptr) = t;
        else
            ptr = new T(t);
    }
    void bar(const T& t) { 
        if(ptr)
            (*ptr) = t;
        else
            ptr = new T(t);
    }
    void bar(T&& t) { 
        if(ptr)
            (*ptr) = std::move(t);
        else
            ptr = new T(std::move(t));
    } 
};

假設您調用了foo.bar(three()); 沒有其他兩個調用:

您為什么認為這行得通? 您的代碼本質上與此等效:

int * p;
*p = 3;

這是未定義的行為,因為p沒有指向類型為int的有效變量。

沒有理由使該代碼失敗。 ptr將指向由先前對bar調用創建的現有int對象,而第三個重載只會將新值分配給該對象。

但是,如果您改為這樣做:

int main() {
    Foo<int> foo;

    int a = 3;
    const int b = 3;
    foo.bar(three()); // <--- UB

    return 0;
}

那個foo.bar(three()); 行將具有未定義的行為(這並不意味着任何異常),因為ptr將不是指向int對象的有效指針。

這里的“不安全”之處在於,在為ptr分配新對象之前,您應該擔心ptr實際指向的對象的命運。

foo.bar(three()); 

這是不安全的,因為您必須在調用它之前授予它ptr實際上指向的東西。 在您的情況下,它指向由foo.bar(b);創建的foo.bar(b);

但是foobar(b)使ptr指向一個新對象,而忘記了foobar(a)創建的對象

可以使用更合適的代碼

template<class T>
class Foo
{
    T* p;
public:
    Foo() :p() {}
    ~Foo() { delete p; }

    void bar(T& t) { delete p; ptr = new T(t); }
    void bar(const T& t) { delete p; ptr = new T(t); }
    void bar(T&& t) 
    { 
        if(!ptr) ptr = new T(std::move(t));
        else (*ptr) = std::move(t); 
    } 
}

;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM