【问题标题】:What happen when a lvalue assigned to a rvalue reference? No destruction of the temporary object?当左值分配给右值引用时会发生什么?没有销毁临时对象?
【发布时间】:2023-03-08 18:55:01
【问题描述】:
#include <iostream>
using namespace std;
#include <cstring>

class Word{
    private:
        char* ptr = nullptr;
    public:
        Word(){
            cout << "default constructor" << endl;
        }
        Word(const char* sentence):ptr{new char [strlen(sentence)+1]}{
            strcpy(ptr, sentence);
            cout << "conversion constructor: " << ptr << endl;
        }
        Word(const Word& w):ptr{new char [strlen(w.ptr)+1]}{
            strcpy(ptr, w.ptr);
            cout << "copy constructor: "<< ptr << endl;
        }
        ~Word(){
            cout << "destructor: " << ptr << endl;
        }
};

int main(){
    Word a ("A stands for apple!");
    Word&& b = "B stands for Banana, rvalue ref";
    b = a;
}

我的 Eclipse 结果:

conversion constructor: A stands for apple!
conversion constructor: B stands for Banana, rvalue ref
destructor: A stands for apple!
destructor: A stands for apple!

我的期望:

conversion constructor: A stands for apple!
conversion constructor: B stands for Banana, rvalue ref
destructor: B stands for Banana, rvalue ref
destructor: A stands for apple!
destructor: A stands for apple!

我被这一步弄糊涂了。

b = a;

当a被赋值给b时,它可以假设首先破坏b持有的临时对象(cstring为“B代表Banana,rvalue ref”),然后将a的值赋给b。为什么在 Eclipse 的结果中,它没有执行临时对象的销毁?

【问题讨论】:

  • 请注意,所有您在此类中新加入的char[]s 已泄露。编译器允许(在 C++17 中要求)省略一些复制和移动,明确即使会有副作用,比如你的cout &lt;&lt; "copy constructor: "&lt;&lt; ptr &lt;&lt; endl;

标签: c++ rvalue-reference temporary temp destruction


【解决方案1】:

b = a; 正在调用赋值运算符,而不是复制构造函数。如果显式删除,代码将无法编译:

// trimmed...
Word(const Word& w):ptr{new char [strlen(w.ptr)+1]}{
    strcpy(ptr, w.ptr);
    cout << "copy constructor: "<< ptr << endl;
}
Word& operator = (const Word& w) = delete;

编译行:

$ g++ rvalue-ref.cpp -o rvalue-ref
rvalue-ref.cpp: In function ‘int main()’:
rvalue-ref.cpp:45:9: error: use of deleted function ‘Word& Word::operator=(const Word&)’
     b = a;
         ^
rvalue-ref.cpp:20:15: note: declared here
         Word& operator = (const Word& w) = delete;
               ^~~~~~~~

编译器将提供一个默认赋值运算符,因此您问题中的代码就是利用它。要查看发生了什么,请添加复制赋值和移动赋值运算符。

Word& operator = (const Word& w) {
    auto temp = new char [strlen(w.ptr)+1];
    strcpy(temp, w.ptr);
    delete [] ptr;
    ptr = temp;
    cout << "assignment operator: " << ptr << endl;
    return *this;
}
Word& operator = (Word&& w) {
    std::swap(ptr, w.ptr);
    cout << "swap operator: " << ptr << endl;
    return *this;
}

有了这些,我得到了预期的输出:

conversion constructor: A stands for apple!
conversion constructor: B stands for Banana, rvalue ref
assignment operator: A stands for apple!
destructor: A stands for apple!
destructor: A stands for apple!

顺便说一句,您正在泄漏内存。你的析构函数应该是这样的:

~Word(){
    cout << "destructor: " << ptr << endl;
    delete [] ptr;
}

$ valgrind ./rvalue-ref

==10736== Memcheck, a memory error detector
==10736== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10736== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10736== Command: ./rvalue-ref
==10736== 
conversion constructor: A stands for apple!
conversion constructor: B stands for Banana, rvalue ref
assignment operator: A stands for apple!
destructor: A stands for apple!
destructor: A stands for apple!
==10736== 
==10736== HEAP SUMMARY:
==10736==     in use at exit: 0 bytes in 0 blocks
==10736==   total heap usage: 5 allocs, 5 frees, 73,800 bytes allocated
==10736== 
==10736== All heap blocks were freed -- no leaks are possible
==10736== 
==10736== For counts of detected and suppressed errors, rerun with: -v
==10736== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

您还可以使用复制/交换习语(如下)来实现赋值运算符。由于临时原因,这将添加额外的构造函数/析构函数输出,但总体而言这是一个很好的做法。

Word& operator = (const Word& w) {
    Word temp(w);
    std::swap(ptr, temp.ptr);
    cout << "assignment operator: " << ptr << endl;
    return *this;
}

【讨论】:

  • 这在某种程度上并不是一个真正的答案,而且这些运算符并不是真正必要的,特别是因为您的实现改变了原始行为。
  • 使用 copy-and-swap 来实现您的复制赋值运算符会使事情变得不太清楚。我花了一点时间才弄清楚那个额外的复制构造函数调用是从哪里来的。
  • @VTT - 原始代码正在获取默认赋值运算符。此版本更改了原始行为,仅足以显示实际发生的情况。
  • 不是真的,这段代码引入了另一个Word 对象,导致额外的构造函数和额外的析构函数调用。我想你应该像股票运营商 + 打印消息一样做。我的意思是 destructor: B stands for Banana, rvalue ref 只是因为你改变了原来的行为而被打印出来。
  • @Red.Wave 这是另一个问题的答案。 OP 想知道他为什么要观察他观察到的东西
【解决方案2】:

你的期望是错误的。破坏不能比建筑多。

当a被赋值给b时,它可以假设首先销毁临时对象

没有。 b 指的是临时对象。分配给对象不会导致对象被销毁。

发生的情况是:Word 隐式生成的赋值运算符将分配所有成员。因此,在赋值之后,b.ptr 的先前值被泄露,并且与a.ptr 具有相同的值(指向相同的字符串)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-01-10
    • 2016-04-29
    • 2021-03-08
    • 2012-03-24
    • 2016-01-20
    • 2016-03-06
    • 1970-01-01
    • 2020-04-10
    相关资源
    最近更新 更多