列表类在析构函数中崩溃,为什么?

list class is crashing in the destructor, why?

提问人:Dov 提问时间:4/28/2023 更新时间:4/28/2023 访问量:79

问:

以下代码使用 placement new 来移动每个值。 复制构造函数和运算符 = 已被删除,因此分配新内存和复制旧内存的唯一位置是在 add 函数中。

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

template<typename T>
class DynArray {
private:
  T* arr;
  uint32_t size;
  void* operator new(size_t size, void* loc) {
    return loc;
  }
public:
  DynArray() : arr(nullptr), size(0) {}
  ~DynArray() {
     delete [] arr;      
  }
  DynArray(const DynArray& orig) = delete;
  DynArray& operator =(DynArray copy) = delete;
  DynArray(DynArray&& orig) : arr(orig.arr), size(orig.size) {
    orig.arr = nullptr;
  }

  void add(const T& v) {
    const T* old = arr;
    arr = (T*)new char[(size+1)*sizeof(T)];
    char* temp = (char*) arr;
    for (uint32_t i = 0; i < size; i++, temp += sizeof(T))
      new(temp) T(old[i]); // this SHOULD do a move?
    new (temp) T(v);
    size++;
    delete [] (char*)old; // don't call destructors for strings, they are moved
  }
  friend ostream& operator <<(ostream& s, const DynArray& list) {
    for (uint32_t i = 0; i < list.size; i++)
      s << list.arr[i] << ' ';
    return s;
  }
};

DynArray<string> f(int n) {
  DynArray<string> ans;
  for (int i = 0; i < n; i++)
    ans.add("ab");
  return ans;
}

int main() {
    DynArray<string> c;
    c.add("x");
    c.add("y");
    c.add("zzzz");
    cout << c << '\n';

    DynArray<string> d = f(3); // calls move constructor
    cout << d << '\n';
    // code crashes in destructor of d, why>
}

问题的根源是什么?

C++ 放置 - 新 stdmove

评论

1赞 Mike Vine 4/28/2023
您仍然需要运行从项目移动的析构函数。
1赞 Mike Vine 4/28/2023
此外,您的移动构造函数应设置orig.size=0;
1赞 Mike Vine 4/28/2023
最后,您需要实现 或 移动赋值运算符。=delete

答:

2赞 Artyer 4/28/2023 #1

您的指针被分配为:错误的类型。delete[]T*new char[...]

您必须将其作为其原始类型(即 )删除。delete[] reinterpret_cast<char*>(arr)

在此之前,您必须手动调用每个值的析构函数。移动的对象也必须被摧毁:

  ~DynArray() {
    // Destroy in reverse order of construction
    for (T* p = arr + size; p != arr;) {
      (--p)->~T();
    }
    delete [] reinterpret_cast<char*>(arr);      
  }

  void add(const T& v) {
    // ...
    for (T* p = old + (old_size); p != old;) {
      (--p)->~T();
    } 
    delete [] reinterpret_cast<char*>(old);
  }

您可能应该将 / 替换为 / 或 /。new char[size]delete[] ptr::operator new(size)::operator delete(ptr)std::allocator_traits<std::allocator<T>>::allocate(alloc, size)std::allocator_traits<std::allocator<T>>::deallocate(alloc, ptr, size)

也不动。 会移动。您确实需要考虑异常安全性:如果其中一个构造函数抛出,则需要销毁先前构造的对象。或者,当在新列表中构造对象时,列表本身处于不稳定状态,如果移动构造函数访问列表,则情况很糟糕。您的功能也是不必要的。new(temp) T(old[i])new(temp) T(std::move(old[i]))operator new

最后,如果这不是一个学习练习,只需使用std::vector<T>