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

发布于 2024-12-06 10:51:05 字数 834 浏览 0 评论 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 ,而无需直接访问此指针并进行复制?

Consider the following piece of code:

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;
}

My question is, why does the third overload Foo::bar(T&& t) crash the program? What exactly is happening here? Does the parameter t get destroyed after the function returns?

Furthermore, let's assume that the template parameter T was a very large object with a very costly copy constructor. Is there any way to use RValue References to assign it to Foo::ptr without directly accessing this pointer and making a copy?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

你的笑 2024-12-13 10:51:05

在这一行
void bar(T&& t) { (*ptr) = t; } } // <--- 不安全!
您可以取消引用未初始化的指针。这是未定义的行为。
您必须首先调用 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));
    } 
};

In this line
void bar(T&& t) { (*ptr) = t; } // <--- Unsafe!
you can dereference an uninitialized pointer. This is undefined behavior.
You must call one of the two other version of bar first because you need to create the memory for your object.
So I would do ptr = new T(std::move(t));.
If your type T supports moving the move constructor will get called.

Update

I would suggest something like that. Not sure if you need the pointer type within 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
};

This would avoid memory leaks which are also quite easy with your approach.
If you really need the pointer in your class foo how about that:

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));
    } 
};
呆橘 2024-12-13 10:51:05

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

但是,如果您这样做:

int main() {
    Foo<int> foo;

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

    return 0;
}

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

There's no reason for that to fail in that code. ptr will point to an existing int object created by the previous calls to bar and the third overload will just assign the new value to that object.

However, if you did this instead:

int main() {
    Foo<int> foo;

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

    return 0;
}

That foo.bar(three()); line would have undefined behaviour (which does not imply any exception), because ptr would not be a valid pointer to an int object.

归途 2024-12-13 10:51:05

假设您调用了foo.bar(三());而没有其他两个调用:

您为什么认为这可行?您的代码本质上与此相同:

int * p;
*p = 3;

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

Assuming that you only called foo.bar(three()); without the other two calls:

Why did you think that'd work? Your code is essentially equivalent to this:

int * p;
*p = 3;

That's undefined behaviour, because p isn't pointing to a valid variable of type int.

若言繁花未落 2024-12-13 10:51:05

“不安全”的事情是,在分配给 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); 
    } 
}

The "unsafe"thing, here is that, before assigning to ptr a new object, you should worry about the destiny of what ptr actually points to.

foo.bar(three()); 

is unsafe in the sense that you have to grant -before calling it- that ptr actually point to something. In your case it points to what was created by foo.bar(b);

But foobar(b) makes ptr to point to a new object forgetting the one created by foobar(a)

A more proper code can be

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); 
    } 
}

;

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文