开发者

RValue references, pointers, and copy constructors

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?


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


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.


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.


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

;

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜