如何在C ++异常类析构函数中释放变量

时间:2019-02-05 14:43:43

标签: c++ exception destructor free dynamic-memory-allocation

我正在定义一个新的C ++类,该类的what方法返回一个char*类型,并带有作为构造函数传递的整数值。

最初,我使用string类并从what返回字符串数据。

然后,我尝试在以下代码中使用char*类型:

/* Define the exception here */
class BadLengthException: public exception
{
  public:
    BadLengthException(int strLength)
    {
        strLen = strLength;
        res = (char*)malloc(strLength+1);
        int resultSize = sprintf(res, "%d", strLen);
    }
    ~BadLengthException() throw()
    {
        free(res);
    }
    virtual const char* what() const throw()
    {
      return res;
    }
  private:
    int strLen;
    char* res;
};

但是在释放malloc分配的变量时遇到问题:它给出了此异常:

pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6

那为什么呢?在哪里以及如何在Exception类中释放动态分配的变量?

编辑

这是一个最小的完整工作示例。该程序将要求用户输入。第一个是一个数字,指定以下输入的数量。其他输入将是字符串。如果字符串小于5,则会引发上述异常。

只需输入:1,然后输入Me

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

/* Define the exception here */
class BadLengthException: public exception
{
  public:
    BadLengthException(int strLength)
    {
        strLen = strLength;
        res = (char*)malloc(strLength+1);
        int resultSize = sprintf(res, "%d", strLen);
    }
    ~BadLengthException() throw()
    {
        free(res);
    }
    virtual const char* what() const throw()
    {
      return res;
    }
  private:
    int strLen;
    char* res;
};



bool checkUsername(string username) {
    bool isValid = true;
    int n = username.length();
    if(n < 5) {
        throw BadLengthException(n);
    }
    for(int i = 0; i < n-1; i++) {
        if(username[i] == 'w' && username[i+1] == 'w') {
            isValid = false;
        }
    }
    return isValid;
}

int main() {
    int T; cin >> T;
    while(T--) {
        string username;
        cin >> username;
        try {
            bool isValid = checkUsername(username);
            if(isValid) {
                cout << "Valid" << '\n';
            } else {
                cout << "Invalid" << '\n';
            }
        } catch (BadLengthException e) {
            cout << "Too short: " << e.what() << '\n';
        }
    }
    return 0;
}

编辑2

使用字符串的原始类如下:确实有效

class BadLengthException: public exception
{
  public:
    BadLengthException(int strLength)
    {
        res = to_string(strLength);
    }
    virtual const char* what() const throw()
    {
      return res.c_str();
    }
  private:
    string res;
};

3 个答案:

答案 0 :(得分:6)

这与异常无关。您的课程无法安全复制。

如果要编写这样的类,则需要使其遵循rule of three

发生的事情是您的异常对象正在被复制,它复制了指针,因此您将同一指针释放了两次。

但是执行此操作的简单方法是使用std::string而不是分配自己的内存。

class BadLengthException: public exception
{
public:
    BadLengthException(int strLength) : strLen(strLength), res(std::to_string(strLength))
    {
    }
    virtual const char* what() const throw()
    {
      return res.c_str();
    }
  private:
    int strLen;
    std::string res;
};

答案 1 :(得分:1)

异常应该不会导致异常主题本身;他们应该是noexcept。因此,通常不建议动态内存分配/取消分配-隐式(例如,使用std :: string ...)或显式(new / delete,malloc / free ...)。一种更好的方法是使用静态char数组:

class BadLengthException:
    public std::exception
{
public:
    BadLengthException(size_t len){
        std::snprintf(res, bufsz, fmt(), len);
    };
    ~BadLengthException()=default;
    virtual const char* what() const {return res;};
private:
    static auto& fmt() {return "bad length [%dz]";};
    static constexpr size_t fmtsz=sizeof(fmt());
    static constexpr size_t intsz=std::numeric_limits<size_t>::digits10;
    static constexpr size_t bufsz=fmtsz+intsz;
    char res[bufsz];
};

答案 2 :(得分:-1)

我将添加另一个答案,尝试将所有内容放在一起。

问题

问题如下。考虑以下示例:

double division(int a, int b) {
   if( b == 0 ) {
      throw "Division by zero condition!";
   }
   return (a/b);
}

在另一个块中,该函数将被调用:

double c;
try
{
    c =division(d,f)
}
catch( ExceptionName e ) {
    ...
}

通常在被调用的函数(在上面的示例中为division)中引发异常(即,生成实例),但是它们会被代码的其他部分(直接调用函数或更多外部函数)捕获。为了使异常实例从生成的地方到捕获到的地方都可用,将复制该实例。

异常实例的复制显然是使用复制分配构造函数执行的(请注意=)。由于我没有声明一个,所以使用默认的一个。 这个默认的构造函数具有以下指针行为:而不是复制指针值,而是复制指针值本身(即地址),这样现在我有了另一个指向相同地址的实例。

假设上面的division函数中发生异常。假设执行了实例的副本。从函数返回时,原始实例将被销毁,因为它位于堆栈内存中,因此将调用析构函数,并将释放指针。但是,指针的内存也由新副本共享。

当副本尝试使用其指针时,将出现错误,因为它不是有效的指针。在我的情况下,它的析构函数正在尝试free使用它,但是它已经被释放:因此出现了错误。

5规则

出现问题是因为我违反了C ++ 11中的5规则(以前是3规则)。

该规则指出,当手动使用和管理资源时(在我的情况下,内存就是资源),并且如果类实现以下成员之一,则另一个成员也应被覆盖:

  1. 析构函数
  2. 复制构造函数
  3. 复制分配运算符
  4. 移动副本构造器
  5. 移动分配运算符

4和5是2和3的对应物,但带有rvaluesherelvalues rvalues上的好帖子)

可能的解决方案

最简单的方法是使用string(已在问题,注释和答案中指定),以便由字符串类自动管理内存。

另一种替代方法(如在另一个答案中所述)是使用共享指针。

尽管不方便,但为了与问题保持一致,此处使用纯指针实现。重新构造了构造函数和析构函数,以便在复制构造函数中为其分配新的内存,而不是使用另一个实例分配的内存。

/* Define the exception here */
class BadLengthException: public exception
{
  public:
    BadLengthException(int strLength)
    {
        cout<<"Constructor\n";
        strLen = strLength;
        res = (char*)malloc(strLen+1);
        int resultSize = sprintf(res, "%d", strLen);
    }


    /*assignment operator*/
    BadLengthException& operator= (const BadLengthException &other)
    {
        cout<<"copy assignment constructor"<<endl;
        if(&other == this)
        {
            return *this;
        }
        strLen = other.strLen;
        res = (char*)malloc(strLen+1);
        int resultSize = sprintf(res, "%d", strLen);
        return *this;
    }

    /*copy constructor*/
    BadLengthException(BadLengthException& other)
    {
        cout<<"copy constructor\n";
        *this = other;

    }

    BadLengthException(BadLengthException&& other)
    {
        cout<<"move constructor "<<endl;
        *this = other;
    }


    BadLengthException& operator=(BadLengthException&& other)
    {
      cout<<"move assignment operator"<<endl;
      if(&other == this)
      {
          return *this;
      }
      *this = other;
      return *this;

    }

    /*class destructor*/
    ~BadLengthException() throw()
    {
        cout<<"destructor"<<endl;
        free(res);
    }

    virtual const char* what() const throw()
    {
      return res;
    }
  private:
    int strLen;
    char* res;
};

测试课程的示例:

int main(int argc, char *argv[])
{
  {
    cout<<"-----Expecting copy constructor------\n";
    BadLengthException e(10);
    BadLengthException e1(e);
    cout<<"10: "<<e1.what()<<endl;
  }
  cout<<endl<<endl;
  {
    cout<<"-----Expecting copy assignment operator------\n";
    BadLengthException e(10);
    BadLengthException e2 = e;
    cout<<"10: "<<e2.what()<<endl;
  }
  cout<<endl<<endl;
  {
    cout<<"-----move assignment operator------\n";
    BadLengthException e3(1);
    e3 = BadLengthException(33);
  }
  {
    cout<<"-----move constructor------\n";
    BadLengthException e4 = BadLengthException(33);
  }
  {
    cout<<"-----move constructor------\n";
    BadLengthException e5(BadLengthException(33));
  }
  cout<<"-----6------\n";
  BadLengthException e6(1), e6_1(2);
  e6 = std::move(e6_1);
  return 0;
}