【问题标题】:push_back using the move constructor does not call the destructor?使用move构造函数的push_back不调用析构函数?
【发布时间】:2021-02-18 10:16:24
【问题描述】:

以下代码创建一个临时对象 A 并将其推入向量中。 复制构造函数被删除,并在 push_back 期间调用移动构造函数。我不确定这段代码的设计是否正确,肯定有内存泄漏。

#include<iostream>
#include<vector>
using namespace std;

class A
{
private:
  char* ptr; 

public:
  A(const string& str)
  {
    ptr = new char[str.size()];
    copy(str.begin(), str.end(), ptr);
    cout << ptr << " Constructor\n" ;
  }

  A(const A& a) = delete;    // copy constructor
  
  A( A&& a)                
  {
    cout << "Move constructor\n";
    ptr = a.ptr;
    a.ptr = nullptr;
  }

  ~A()
  {
    cout << ptr << " Destructor\n";
    delete[] ptr;
    ptr = nullptr;
  }

  void print()
  {
    cout << ptr << endl;
  }
};



int main()
{
  vector<A> v;
  v.reserve(5);

  v.push_back( A("hello")  );
  v[0].print();
  cout << "here" << endl;
  
  return 0;
}

输出是:

    hello Constructor
    Move constructor

为什么print函数不打印,析构函数没有被调用?谢谢

【问题讨论】:

  • 析构函数可能会崩溃,因为您尝试打印 nullptr
  • 我认为问题可以简化为std::cout &lt;&lt; (char*)nullptr; - 我不确定打印应该如何表现。
  • std::cout &lt;&lt; (char *)nullptr 将调用接受两个参数的operator&lt;&lt;()(第一个是通过引用对模板化std::basic_ostream 进行特化,第二个是const char *)。该运算符假定第二个参数指向一个以空字符结尾的字符串(的第一个字符),否则具有未定义的行为。由于(char *)nullptr 不指向以空结尾的字符串,因此行为未定义。
  • 我认为您的 char* 缺少空终止符; std::string::size() 不包括它的空终止符。
  • 这并没有解决问题,但是析构函数中的ptr = nullptr; 并没有完成任何事情。物体正在消失; ptr 在析构函数返回后将不存在,因此它的值无关紧要。

标签: c++


【解决方案1】:

正如评论中指出的,您的问题出在此处:

~A()
{
  cout << ptr << " Destructor\n";
  delete[] ptr;
  ptr = nullptr;
}

当你到达析构函数时,对象已经从 ptr == nullptr 移动了。 &lt;&lt;char* 重载需要一个以 null 结尾的字符串。如果ptr 不指向以空字符结尾的字符串,您将调用未定义的行为。

如果你改变它,你会得到预期的输出

cout << static_cast<void*>(ptr) << " Destructor\n";

因为void* 的重载只会打印指针的值。

Working Demo

【讨论】:

  • 我自己会用 void 和 char 分支做一个 if
  • @Yakk if (ptr) std::cout &lt;&lt; ptr; ?修复了 nullptr 的问题,但如果 ptr 未指向 c 字符串,仍可能失败。不确定,我认为最好保持安全。
【解决方案2】:

您的代码具有未定义的行为,因为您实际上是在这样做

std::cout << (char*)nullptr;

回想一下,您的 moved-from 对象的指针设置为 nullptr

这是对那些标准库插入器的先决条件:

[ostream.inserters.character]

template<class charT, class traits>
  basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>& out, const charT* s);
template<class charT, class traits>
  basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>& out, const char* s);
template<class traits>
  basic_ostream<char, traits>& operator<<(basic_ostream<char, traits>& out, const char* s);
template<class traits>
  basic_ostream<char, traits>& operator<<(basic_ostream<char, traits>& out, const signed char* s);
template<class traits>
  basic_ostream<char, traits>& operator<<(basic_ostream<char, traits>& out,
                                          const unsigned char* s);

3 前提条件s 不是空指针。

添加一个空检查,或者只是不在析构函数中打印指针值。

【讨论】:

  • @largest_prime_is_463035818 -¯\_(ツ)_/¯ 。我把钟摆摆回了你的回答。打印void* 很好。
猜你喜欢
  • 2011-04-16
  • 2015-02-21
  • 1970-01-01
  • 2013-05-06
  • 2019-03-22
  • 2014-03-13
  • 1970-01-01
  • 2021-07-19
  • 2017-02-08
相关资源
最近更新 更多