【问题标题】:C++ Dynamic Member Arrays Deleted Right Before Destructor Called在调用析构函数之前删除的 C++ 动态成员数组
【发布时间】:2020-04-01 11:44:38
【问题描述】:

我正在从事一个 AI 项目,并已开始实现一个 NeuralNetwork 类。我只是想了解一些基本的东西,所以我使用了一些 malloc 语句和一些放置新闻,最后删除 []s。

但是,一旦 NeuralNetwork 对象(在 main 函数中的堆栈上创建)即将被删除(我在析构函数的开头设置了一个断点),我的数组似乎已被提前删除(值 0xcccccccc)并且因此 delete[] 语句会引发访问冲突。

通过进一步调查,我发现这种删除发生在最后一个 Vector 对象被销毁和我的 NeuralNetwork 对象的析构函数开始被调用之间。

我确保同时实现了复制构造函数和赋值运算符,以确保在我注意到的情况下不会发生复制。

我真的对这个问题感到困惑,希望有人能发现我的错误。源代码如下:

NeuralNetwork::NeuralNetwork(const std::initializer_list<size_t>& l):
    m_size(l.size() - 1),
    m_weights(static_cast<Matrix<double>*>(malloc(sizeof(Matrix<double>) * m_size))),
    m_biases(static_cast<Vector<double>*>(malloc(sizeof(Vector<double>) * m_size)))
{
    size_t index = 0;
    auto itr = l.begin();

    for (auto next = itr + 1; next != l.end(); ++next, ++itr, ++index)
    {
        new (m_weights + index) Matrix<double>(*next, *itr);
        new (m_biases + index) Vector<double>(*next);
    }
}

NeuralNetwork::NeuralNetwork(const NeuralNetwork& nn) :
    m_size(nn.m_size),
    m_weights(static_cast<Matrix<double>*>(malloc(sizeof(Matrix<double>)* m_size))),
    m_biases(static_cast<Vector<double>*>(malloc(sizeof(Vector<double>)* m_size)))
{
    for (size_t index = 0; index < m_size; ++index)
    {
        new (m_weights + index) Matrix<double>(nn.m_weights[index]);
        new (m_biases + index) Vector<double>(nn.m_biases[index]);
    }
}

NeuralNetwork::NeuralNetwork(NeuralNetwork&& nn) noexcept :
    m_size(nn.m_size),
    m_weights(nn.m_weights),
    m_biases(nn.m_biases)
{
    nn.m_size = 0;
    nn.m_weights = nullptr;
    nn.m_biases = nullptr;
}

NeuralNetwork::~NeuralNetwork()
{
    delete[] m_weights; // exception thrown here, value is 0xcccccccc, nullptr
    delete[] m_biases;
}

主要代码:

int main()
{
    NeuralNetwork nn{ 2, 1 };

    Vector<double> input(2);
    input.Get(0) = 0.5;
    input.Get(1) = 0.25;

    Vector<double> output = nn.Forward(input); // just does the math, nothing special

    for (size_t i = 0; i < output.GetSize(); ++i)
        std::cout << output.Get(i) << " ";

    std::cout << std::endl;
}

如果缺少任何重要的源代码,请告诉我,谢谢!

【问题讨论】:

  • 你为什么要这么做static_cast+malloc魔法?此外,delete[] 是一对new[]。顺便提一句。 new,人们通常想存储他们的结果,你只需在各种循环中调用它们。
  • 我将它们存储在权重和偏差数组中。 m_weights + index 是指向存储它们的地址的指针
  • 我使用 malloc + cast 因为我不想默认构造数组
  • 在is初始化后在内存上添加数据断点,可以找到修改的地方

标签: c++


【解决方案1】:

malloc/free 和 new/delete 和 new[] / delete[] 有很大区别

malloc 将分配一块未格式化的内存,而 free 将释放它

new 将分配并初始化该区域,而 delete 将调用析构函数

有时使用 malloc 和 delete 可能会起作用,但这是个坏主意

new[] 还会在返回的内存之前保留一些额外的信息,以了解需要删除多少对象

有用的链接: https://www.geeksforgeeks.org/placement-new-operator-cpp/

【讨论】:

    【解决方案2】:

    你不应该写这样的代码:

    • 您永远不必在 C++ 程序中使用 malloc/free。
    • 分配和取消分配应该匹配。
    • 与您尝试避免的默认初始化相比,动态内存分配肯定会产生更多开销。
    • 如果初始值设定项列表为空,您的代码将会失败。
    • 代码存在内存泄漏。
    • 如果定义了复制构造函数,则还应定义赋值运算符(与移动构造函数相同)。

    标准库已经做了最相关的优化。例如,对于std::vector,项目的构造函数只会在您调用emplace_back 时被调用。

    您真的应该编写标准代码。为边际性能改进而编写错误代码是不值得的。

    你的类声明应该看起来像:

    class NeuralNetwork
    {
    public:
        NeuralNetwork(const std::initializer_list<size_t>& l);
        // Other constructors as appropriate here…
    
    private:
        std::vector<Matrix<double>> m_weights;
        std::vector<Vector<double>> m_biases;
    };
    

    并且构造函数应该看起来类似(代码未测试):

    NeuralNetwork::NeuralNetwork(const std::initializer_list<size_t>& l):
    {
        if (l.empty()
        {
            // might assert in debug or throw an exception...
            return;
        }
    
        m_weights.reserve(m_size);
        m_biases.reserve(m_size);
    
        auto itr = l.begin();
        for (auto next = itr + 1; next != l.end(); ++next, ++itr, ++index)
        {
            m_weights.emplace(*next, *itr);
            m_biases.emplace(*next);
        }
    }
    

    其他构造函数、赋值运算符和析构函数应该更容易实现、更健壮并且性能非常相似。

    【讨论】:

      【解决方案3】:

      由于您已经在使用 C++11 功能,您还可以使用 std::vector::emplace_back(),它将在内部处理放置 new。

      例子:

      #include <iostream>
      #include <initializer_list>
      #include <vector>
      
      template<class T> class Matrix {
      public:
          Matrix() {std::cout << "Matrix() " << this << std::endl;}
          Matrix(int width,int height):w(width),h(height) {std::cout << "Matrix(" << w << "x" << h << ") " << this << std::endl;}
          ~Matrix() {std::cout << "Matrix(" << w << "x" << h << ") " << this << " goodbye" << std::endl;}
      private:
          int w,h;
      };
      
      class NN {
      public:
          NN()=default;
          NN(const std::initializer_list<size_t> &l);
      private:
          std::vector<Matrix<double>> m_weights;
      };
      
      NN::NN(const std::initializer_list<size_t> &l) {
          m_weights.reserve(l.size()-1); // or deal with move constructors
          auto itr = l.begin();
          for (auto next = itr + 1; next != l.end(); ++next, ++itr)
          {
              m_weights.emplace_back(*next, *itr);
          }
      }
      
      int main() {
          NN test{2,3,3,2};
          return 0;
      }
      

      输出(来自https://ideone.com/yHGAMc):

      Matrix(3x2) 0x5638f59aae70
      Matrix(3x3) 0x5638f59aae78
      Matrix(2x3) 0x5638f59aae80
      Matrix(3x2) 0x5638f59aae70 goodbye
      Matrix(3x3) 0x5638f59aae78 goodbye
      Matrix(2x3) 0x5638f59aae80 goodbye
      

      所以没有涉及到默认的构造函数,对象被正确的销毁了。

      【讨论】:

        猜你喜欢
        • 2017-08-28
        • 2012-09-28
        • 2015-12-05
        • 1970-01-01
        • 2013-12-08
        • 2011-11-05
        • 2015-12-31
        • 1970-01-01
        • 2013-05-21
        相关资源
        最近更新 更多