RValue引用,指针和复制构造函数

时间:2011-09-23 16:02:28

标签: c++ templates memory pointers rvalue-reference

考虑以下代码:

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而无需直接访问此指针并制作副本?

4 个答案:

答案 0 :(得分:3)

在这一行中 void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
你可以取消引用一个未初始化的指针。这是未定义的行为。 您必须首先调用另一个版本的bar之一,因为您需要为对象创建内存 所以我会做ptr = new T(std::move(t)); 如果您的类型T支持移动,则将调用移动构造函数。

<强>更新

我会建议这样的事情。不确定你是否需要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));
    } 
};

答案 1 :(得分:0)

假设您在没有其他两个电话的情况下调用foo.bar(three());

为什么你认为这有用?您的代码基本上与此相同:

int * p;
*p = 3;

这是未定义的行为,因为p未指向int类型的有效变量。

答案 2 :(得分:0)

没有理由在该代码中失败。 ptr将指向先前调用int创建的现有bar对象,第三个重载只会将新值分配给该对象。

但是,如果你这样做了:

int main() {
    Foo<int> foo;

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

    return 0;
}

foo.bar(three());行会有未定义的行为(这并不意味着任何异常),因为ptr不是指向int对象的有效指针。

答案 3 :(得分:0)

“不安全”的事情,就是在给ptr分配一个新对象之前,你应该担心ptr实际指向的命运。

foo.bar(three()); 

在某种意义上是不安全的,你必须在调用之前授予它 - ptr实际上指向了什么。在您的情况下,它指向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); 
    } 
}