如何在 C++ Exception class 析构函数中释放变量
How to free variable in C++ Exception class destructor
我正在定义一个新的 C++ class,其 what
方法 returns 一个 char*
类型,其值作为构造函数传递。
最初我使用 string
class 并从 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
那是为什么呢?我应该在哪里以及如何释放异常中的动态分配变量 class?
编辑
这是一个最小的工作完整示例。该程序将要求用户输入。第一个是一个数字,指定以下输入的数量。其他输入将是字符串。如果字符串短于 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 如下:这个 有效
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;
};
这与异常无关。您的 class 无法安全复制。
如果你要写一个 class 这样的,那么你需要让它遵循 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;
};
我将尝试将所有内容放在一起添加另一个答案。
问题
问题如下。考虑这个例子:
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的规则)。
该规则规定,当手动使用和管理资源时(在我的例子中,内存是资源)并且如果 class 实现了以下成员之一,那么另一个也应该被覆盖:
- 析构函数
- 拷贝构造函数
- 复制赋值运算符
- 移动复制构造函数
- 移动赋值运算符
4 和 5 对应于 2 和 3,但是 rvalues
(here lvalues
rvalues
上的 post)
可能的解决方案
最简单的方法是使用 string
,正如问题、评论和答案中已经指定的那样,内存由字符串 class.
自动管理
另一种选择(在另一个答案中 posted)是使用共享指针。
虽然不方便,但为了与问题连贯,这里使用纯指针实现。构造函数和析构函数被重新定义,以便在复制构造函数中为它们分配新内存,而不是使用另一个实例分配的内存。
/* 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;
};
测试class的示例:
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;
}
异常本身不应导致异常;他们应该是 noexcept
。因此,动态内存 alloc/dealloc - 无论是隐式的(例如使用 std::string...)还是显式的(new/delete,malloc/free...) - 通常都是不鼓励的。一种更好的方法是使用静态字符数组:
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];
};
我正在定义一个新的 C++ class,其 what
方法 returns 一个 char*
类型,其值作为构造函数传递。
最初我使用 string
class 并从 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
那是为什么呢?我应该在哪里以及如何释放异常中的动态分配变量 class?
编辑
这是一个最小的工作完整示例。该程序将要求用户输入。第一个是一个数字,指定以下输入的数量。其他输入将是字符串。如果字符串短于 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 如下:这个 有效
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;
};
这与异常无关。您的 class 无法安全复制。
如果你要写一个 class 这样的,那么你需要让它遵循 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;
};
我将尝试将所有内容放在一起添加另一个答案。
问题
问题如下。考虑这个例子:
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的规则)。
该规则规定,当手动使用和管理资源时(在我的例子中,内存是资源)并且如果 class 实现了以下成员之一,那么另一个也应该被覆盖:
- 析构函数
- 拷贝构造函数
- 复制赋值运算符
- 移动复制构造函数
- 移动赋值运算符
4 和 5 对应于 2 和 3,但是 rvalues
(here lvalues
rvalues
上的 post)
可能的解决方案
最简单的方法是使用 string
,正如问题、评论和答案中已经指定的那样,内存由字符串 class.
另一种选择(在另一个答案中 posted)是使用共享指针。
虽然不方便,但为了与问题连贯,这里使用纯指针实现。构造函数和析构函数被重新定义,以便在复制构造函数中为它们分配新内存,而不是使用另一个实例分配的内存。
/* 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;
};
测试class的示例:
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;
}
异常本身不应导致异常;他们应该是 noexcept
。因此,动态内存 alloc/dealloc - 无论是隐式的(例如使用 std::string...)还是显式的(new/delete,malloc/free...) - 通常都是不鼓励的。一种更好的方法是使用静态字符数组:
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];
};