1)C++标准库中的异常数目有限,可以在程序中为特定的错误创建更有意义的类名称,而不是使用具有通常名称的异常类,例如runtime_error
2)可以在异常中加入自己的信息,而标准层次结构中的异常只允许设置错误字符串。例如或许想在异常中传递不同的信息。
示例为文件错误定义自己的错误层次结构,从一个泛型类FileError开始:
#pragma once
#include<iostream>
#include<exception>
#include<string>
using namespace std;
class FileError :public exception
{
public:
FileError(const string& fileIn) :mFile(fileIn) {};
virtual const char* what() const noexcept override { return mMgs.c_str(); }
const string& getFileName() { return mFile; }
protected:
void setMessage(const string &message) { mMgs = message; }
private:
string mFile, mMgs;
};
作为一名优秀程序员,应将FileError作为标准异常层次结构的一部分,将其作为exception的子类是恰当的。当编写exception的派生类时,需要重写what()方法,这个方法的原型已经出现过,其返回值作为一个在对象销毁之前一直有效的const char*字符串。在FileError中,这个字符串来自mMsg数据成员,在构造函数中将其设置为“”。FileError的派生类如果想给出不同的消息,就必须用不同的消息设置这个mMsg字符串。
泛型类FileError还包含文件名、访问文件名的公有方法和受保护的设置方法,以便派生类可以设置该消息。
readIntegerFile()中第一种异常情况是无法打开文件。因此,下面编写FileError的派生类FileOpenError:
#pragma once
#include"FileError.h"
#include<string>
#include<iostream>
using namespace std;
class FileOpenError :public FileError
{
public:
FileOpenError(const std::string& fileNameIn);
};
FileOpenError::FileOpenError(const std::string & fileNameIn):FileError(fileNameIn)
{
setMessage("Unable to open" + fileNameIn);
}
FileOpenError修改mMsg字符串,令其表示文件打开错误。
readInterFile()的第二种异常情况是无法正确读取文件。对于这一异常,或许应该包含文件中发生错误的行为,以及what()返回的错误信息字符串中的文件名。下面是FileError的派生类FileReadError:
#pragma once
#include<iostream>
#include<string>
#include"FileError.h"
#include<sstream>
using namespace std;
class FileReadError :public FileError
{
public:
FileReadError(const string&fileNameIn, int lineNumIn);
int getLineNum() { return mLineNum; }
private:
int mLineNum;
};
FileReadError::FileReadError(const string & fileNameIn, int lineNumIn):FileError(fileNameIn),mLineNum(lineNumIn)
{
ostringstream ostr;
ostr << "Error reading " << fileNameIn << " at line " << lineNumIn;
setMessage(ostr.str());
}
readIntegerFile()函数
正确设置行号,跟踪所读取的行号
#include"stdafx.h"
#include<iostream>
#include"FileOpenError.h"
#include"FileReadError.h"
#include<fstream>
#include<vector>
using namespace std;
/**
readIntegerFile()函数
正确设置行号,跟踪所读取的行号
*/
void readIntegerFile(const string& fileName, vector<int>&dest)
{
ifstream istr;
int temp;
string line;
int lineNumber = 0;
istr.open(fileName);
if (istr.fail())
{
throw FileOpenError(fileName);
}
while (!istr.eof())
{
//read one line from the file
getline(istr, line);
lineNumber++;
//Create a string stream out of the line.
istringstream lineStream(line);
//Read the integers one by one and add them to the vector
while (lineStream >> temp)
{
dest.push_back(temp);
}
if (!lineStream.eof())
{
//we did not reach the end of the string stream
//this means that some error occurred while reading this line.
//throw an exception
throw FileReadError(fileName, lineNumber);
}
}
}
int main()
{
vector<int> myInts;
const string fileName = "C:/Users/Administrator/Desktop/IntegerFile.txt";
try {
readIntegerFile(fileName, myInts);
}
catch (const FileError&e) {
cerr << e.what() << endl;
return 1;
}
for (const auto element : myInts)
{
cout << element << " ";
}
cout << endl;
return 0;
}
txt文件
输出
编写其对象用作异常的类时,有一个诀窍。当某段代码抛出一个异常时,复制被抛出的值或者对象。也就是说,使用复制构造函数从就对象构造新对象。复制是必须的,因为原始对象在堆栈中的位置较高,可能在异常被捕获之前超出作用域(因此会被销毁,其所占的内存会被回收)。因此,如果编写的类的对象将作为异常抛出,对象必须能复制,这意味着如果动态分配了内存,必须编写析构函数、复制构造函数和赋值运算符。
注意:作为异常抛出的对象至少按值复制一次。
异常可能被复制多次,但只有按值(而不是按引用)捕获异常才会如此。
注意:按引用捕获异常对象可以避免不必要的复制。