RValue ссылки, указатели и конструкторы копирования - PullRequest
0 голосов
/ 23 сентября 2011

Рассмотрим следующий фрагмент кода:

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 References, чтобы присвоить его Foo::ptr без прямого доступа к этому указателю и создания копии?

Ответы [ 4 ]

3 голосов
/ 23 сентября 2011

В этой строке
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));
    } 
};
0 голосов
/ 23 сентября 2011

«Небезопасная» вещь в том, что перед тем, как назначить 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); 
    } 
}

;

0 голосов
/ 23 сентября 2011

Нет причин для этого в этом коде.ptr будет указывать на существующий объект int, созданный предыдущими вызовами bar, а третья перегрузка просто назначит этому объекту новое значение.

Однако, если вы сделали это вместо этого:1006 *

int main() {
    Foo<int> foo;

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

    return 0;
}

Эта строка foo.bar(three()); будет иметь неопределенное поведение (что не подразумевает каких-либо исключений), поскольку ptr не будет действительным указателем на объект int.

0 голосов
/ 23 сентября 2011

Предполагая, что вы только звонили foo.bar(three()); без двух других вызовов:

Почему вы думаете, что это сработает?Ваш код по сути эквивалентен этому:

int * p;
*p = 3;

Это неопределенное поведение, потому что p не указывает на допустимую переменную типа int.

...