0

考虑以下代码:

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

4 回答 4

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));
    } 
};
于 2011-09-23T16:13:53.810 回答
0

假设您打电话foo.bar(three());而没有其他两个电话:

为什么你认为那会起作用?您的代码本质上等同于:

int * p;
*p = 3;

这是未定义的行为,因为p它没有指向 type 的有效变量int

于 2011-09-23T16:05:23.687 回答
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

于 2011-09-23T16:11:10.540 回答
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); 
    } 
}

;

于 2011-09-23T16:31:17.653 回答