Почему неименованный объект, возвращаемый по значению, разрушается до вызова его оператора преобразования?

У меня есть функция, которая возвращает объект по значению. Переменная получателя требует вызова оператора внешнего преобразования для этого объекта. Если я создаю возвращенный объект в операторе возврата (RVO), его деструктор вызывается перед оператором внешнего преобразования. Однако, если я назову объект и верну его, оператор внешнего преобразования вызывается до того, как объект будет уничтожен. Это почему?

#include <iostream>

class Ref {
public:
    Ref(int * ptr) : iptr(ptr) {
        std::cout << "Ref Constructed at: " << long(this) << " Pointing to: " << long(ptr) << '\n';
    }
    Ref(Ref & ref) : iptr(ref) {
        std::cout << "Ref Moved to: " << long(this) << '\n';
        ref.iptr = nullptr;
    }

    operator int () {
        std::cout << "Ref-To int: Temp at: " << long(iptr) << '\n';
        return *iptr;
    }

    operator int* () {
        std::cout << "Ref-To int*: Temp at: " << long(iptr) << '\n';
        return iptr;
    }
    ~Ref() {
        delete iptr;
        std::cout << "Ref at: " << long(this) << " Deleted: " << long(iptr) << '\n';
    }
private:
    int * iptr;
};

Ref foo() {
    int * temp = new int(5);
    Ref retVal(temp); 
    std::cout << "Return named Ref\n";
    return retVal;
}

Ref bar() {
    int * temp = new int(5);
    std::cout << "Return anonymous Ref\n";
    return Ref(temp);
}


int _tmain(int argc, _TCHAR* argv[])
{
    std::cout << "*********  Call foo() *************\n";   
    int result = foo();
    std::cout << "\n*********  Call bar() *************\n"; 
    int result2 = bar();
    return 0;
}

Выход из этого:

*********  Call foo() *************
Ref Constructed at: 2356880 Pointing to: 5470024
Return named Ref
Ref-To int*: Temp at: 5470024
Ref Moved to: 2356956
Ref at: 2356880 Deleted: 0
Ref-To int: Temp at: 5470024
Ref at: 2356956 Deleted: 5470024

*********  Call bar() *************
Return anonymous Ref
Ref Constructed at: 2356680 Pointing to: 5470024
Ref-To int*: Temp at: 5470024
Ref Constructed at: 2356968 Pointing to: 5470024
Ref at: 2356680 Deleted: 5470024
Ref-To int: Temp at: 5470024
Press any key to continue . . .

Когда вызывается bar(), ссылка удаляется перед вызовом оператора преобразования, и происходит сбой. Кроме того, я не понимаю, почему преобразование Ref в int * вызывается при построении возвращаемого значения.

1 ответ

Решение

Что здесь происходит

Я не понимаю, почему преобразование Ref в int* вызывается при построении возвращаемого значения.

Это происходит потому, что, по-видимому, MSVC не выполняет RVO в режиме отладки, поэтому "конструктор копирования" (Ref(Ref&)) вызывается, чтобы вернуться из foo функция, и это выполняется:

Ref(Ref & ref) : iptr(ref) {
    // ...
}

где iptrтипа int*, инициализируется неявным преобразованием из ref,

Как bogdan заставил меня заметить, этот ваш "конструктор копирования" действительно имеет семантический конструктор перемещения, и вам, вероятно, следует написать конкретный Ref(Ref&&) вместо этого.

В barУ нас есть, что вы строите Rvalue return Ref(temp) который не может быть привязан к ссылке на lvalue для Ref(Ref&) конструктор, и, следовательно, другой конструктор выбирается и указатель копируется в (без сброса временного).

Когда временный выходит из области видимости, указатель deleteг, и когда построенный объект из bar также выходит из области видимости, тот же указатель удаляется, вызывая неопределенное поведение (которое является причиной вашего сбоя).

C++ способ написать этот класс

У вашего класса много других проблем. С одной стороны, это может привести к различным сбоям и, как правило, не безопасно для памяти. В C++ вы могли бы написать что-то вроде этого для этого класса:

class Ref {
public:

    explicit Ref(std::unique_ptr<int> ptr)
        : iptr(std::move(ptr))
        {}

    int get() const { return *iptr; }
    int* data() const { return iptr.get(); }

private:

    std::unique_ptr<int> iptr;

};

или даже просто std::unique_ptr,

Дело в том, что неявные преобразования и ручное динамическое распределение памяти часто приводят ко многим ошибкам и "сбоям". Избегайте их как чумы в максимально возможной степени.

Другие вопросы по тегам