【问题标题】:What is the proper way to raise exception and handle certain exception type in C++在 C++ 中引发异常和处理某些异常类型的正确方法是什么
【发布时间】:2018-09-13 00:21:59
【问题描述】:

我知道理论上如何创建和处理异常。但是在一个大项目中,一个函数可能会抛出很多不同的异常。

我如何设法解析我可以收到的所有异常?

例如:

我目前正在实现一个数据库客户端。如果查询请求坏表或坏列,我会在低级驱动程序中抛出异常,如下所示:

throw MySQLException("message");

这将导致在客户端的顶层抛出std::runtime_error 异常。所以我添加了以下代码来捕获异常:

try {
  execute(query);
}
catch(std::exception &e) {
  // How do I parse exception content?
}

但是有没有比从e.what() 中提取信息更好的方法来弄清楚它是什么类型的异常? 目的是让每一种异常都对应一个错误码(不修改异常信息)。

欢迎链接到教程。

【问题讨论】:

  • 如果您发现自己经常使用try 块,您可能需要考虑使用Lippencot function
  • 为什么抛出 throw MySQLException 会导致抛出 std::runtime_error?

标签: c++ exception exception-handling


【解决方案1】:

您可以有多个catch 语句,如下所示:

try {
    execute(query);
}
catch (const my_custom_exception_type& e) {
}
catch (const std::runtime_error& e) {
}
catch (const std::exception& e) {
}
catch (...) {
    // fallback - exception object is not any of the above types
}

控制将流入第一个catch 块,其参数类型与异常对象类型兼容。如果没有匹配项并且没有... catch-all,则异常将传播到该 try/catch 块之外。可以在here 找到对确切 try/catch 行为的更准确解释:

当复合语句中的任何语句抛出类型 E 的异常时,它会与 handler-seq 中每个 catch 子句的形参 T 的类型相匹配,按照 catch 子句的列出顺序.如果以下任何一项为真,则例外是匹配项:

  • E 和 T 是同一类型(忽略 T 上的顶级 cv 限定符)
  • T 是对(可能是 cv 限定的)E 的左值引用
  • T 是 E 的明确公共基类
  • T 是对 E 的明确公共基类的引用
  • T 是(可能是 cv 限定的)U 或 const U&(C++14 起),U 是指向成员的指针或指针(C++17 起)类型,E 也是指向成员的指针或指针可通过以下一项或多项隐式转换为 U 的成员(C++17 起)类型
    • 一种标准指针转换,而不是转换为私有、受保护或不明确的基类
    • 资格转换
    • 函数指针转换(C++17 起)
  • T 是指针或指向成员的指针或对 const 指针的引用(C++14 起),而 E 是 std::nullptr_t。

您也可以使用RTTI 来确定@​​987654327@ 的类型,但尽可能避免使用。

【讨论】:

  • 如果是这样,为什么当我没有发现异常时,我会收到以下文本:terminate called after throwing an instance of 'std::runtime_error' 而不是 terminate called after throwing an instance of 'MySQLException'?是由于继承还是我的异常可能已经被捕获并重新发送?
  • @GuillaumeMILAN 如果没有看到完整的代码,我无法判断。 std::terminate() 确实打印了异常对象的具体类型名称,即使对于自定义异常类型,至少在 glibc 中是这样(不确定打印的文本是否是规范的一部分,我对此表示怀疑)。
【解决方案2】:

我不能 100% 确定这是否是您直接寻找的内容,但关于您所做的声明:

But in a big project a function can throw a lot of different exception.

在构建 3D 图形引擎时,我曾在一个大型项目中使用过这种设计。不过,这确实需要一些课程。需要注意的另一件事是,我的代码在设计时考虑了特定于 Windows 的情况。我已经评论了可以用您的特定操作系统实现替换的特定于窗口的代码部分。另一件事是我在许多输出消息中使用了__FUNCTION__,您可以将其替换为__PRETTY_FUNCTION__,因为出于某种原因,Windows 和 Visual Studio 不喜欢它...您还可以替换我使用的命名空间您自己的命名空间名称。如果这不能直接帮助您,那么至少整体设计可能是有用的。您可以创建一个新项目并按原样使用它来查看生成的控制台消息和写入的日志文件。您可以从中获取一些概念,或者您可以扩展和集成您自己的特定错误消息和异常类型,这些错误消息和异常类型会被抛出到这些类中,使其适合您自己的目的。


以下类集成在一起,其中一些可以独立使用或组合使用,并且对于多线程而言应该是线程安全的。

  • ExceptionHandler - 它使用 Logger 类,用于处理抛出和/或发送到 Logger 的消息。
  • Lo​​gger - Singleton 的派生类。它记录到控制台、文本文件或两者。它使用 TextFileWriter 写入文本文件。
  • Singleton - 所有 Singleton Type 类都派生自的基类。
  • TextFileWriter - FileHandler 的派生类。它写入一个文本文件。
  • FileHandler - 所有文件处理类型类派生自的基类。


使用上述类的示例程序:
#include "Logger.h"
#include "ExceptionHandler.h"

#include <sstream>
#include <iostream>

enum ReturnCode {
    ReturnError = -1,
    ReturnOkay = 0
};

// helper function
void quitMessage() {
    std::cout << "\nPress any key and enter to quit.\n";
    std::cin.get();
}

// Example Class that uses the Logger & ExceptionHandler.
class Foo {
public:
    Foo() = default;

    void printFooUsingExceptionHandler() {
        using namespace linx;
        throw ExceptionHandler( __FUNCTION__ + std::string( " using ExceptionHandler" ) );
        // Or can be used this way
        // std::ostringstream stream;
        // stream << __FUNCTION__ << " using ExceptionHandler";
        // throw ExceptionHandler( stream );
    }

    void printFooNotUsingExceptionHandlerButUsingLogger() {
        using namespace linx;
        std::ostringstream stream;
        stream << __FUNCTION__ << " logging to logger, but not using ExceptionHandler";
        Logger::log( stream, Logger::TypeWarning ); // Can use TypeInfo, TypeWarning or TypeError pending on the use case.
    }
};

int main() {
    namespace lx = linx;
    using namespace lx;

    try {
        Logger logger( "logger.txt" );

        // last param is ommited as it is defaults to TypeInfo
        Logger::log( "This is basic info" ); 
        Logger::log( "This is a warning", Logger::TypeWarning );
        Logger::log( "This is an error", Logger::TypeError );
        Logger::log( "Basic console message", Logger::TypeConsole );    

        // Example of a class using the ExceptionHandler & Logger classes
        Foo f;
        // call this first; log to message but don't throw excpetion
        f.printFooNotUsingExceptionHandlerButUsingLogger();
        // call this to throw exception
        f.printFooUsingExceptionHandler();

    } catch( ExceptionHandler& e  ) {
        std::cout << "Exception Thrown: " << e.getMessage() << std::endl;
        quitMessage();
        return ReturnError;
    } catch( ... ) {
        std::cout << __FUNCTION__ << " Caught Unknown Exception" << std::endl;
        quitMessage();
        return ReturnError;
    }

    quitMessage();
    return ReturnOkay;
}

类:


ExceptionHandler.h

#ifndef EXCEPTION_HANDLER_H
#define EXCEPTION_HANDLER_H

#include <string>
#include <sstream>

namespace linx {

class ExceptionHandler final {
private:
    std::string _message;

public:
    explicit ExceptionHandler( const std::string& message, bool saveInLog = true );
    explicit ExceptionHandler( const std::ostringstream& streamMessage, bool saveInLog = true );

    ~ExceptionHandler() = default;
    ExceptionHandler( const ExceptionHandler& c ) = default;
    ExceptionHandler& operator=( const ExceptionHandler& c ) = delete;

    const std::string& getMessage() const;    
};

} // namespace linx

#endif // !EXCEPTION_HANDLER_H

ExceptionHandler.cpp

#include "ExceptionHandler.h"
#include "Logger.h"

namespace linx {

ExceptionHandler::ExceptionHandler( const std::string& message, bool saveInLog ) : 
_message( message ) {
    if( saveInLog ) {
        Logger::log( _message, Logger::TypeError );
    }
}

ExceptionHandler::ExceptionHandler( const std::ostringstream& streamMessage, bool saveInLog ) :
_message( streamMessage.str() ) {
    if( saveInLog ) {
        Logger::log( _message, Logger::TypeError );
    }
}

const std::string& ExceptionHandler::getMessage() const {
    return _message;
}

} // namespace linx

Logger.h

#include <sstream>
#include <array>

// For Windows Console Output
#define VC_EXTRALEAN 
#include <Windows.h>

namespace linx {

class Logger final : public Singleton {
public:
    enum LoggerType {
        TypeInfo = 0,
        TypeWarning,
        TypeError,
        TypeConsole
    };

private:
    std::string _logFilename;
    unsigned    _maxCharLength;

    std::array<std::string, 4> _logTypes;
    const std::string _unknownLogType;

    // This is for Windows Console Output - Can substitue with OS type
    HANDLE _hConsoleOutput;
    WORD   _consoleDefualtColor;
    // --------------------------------------------------------------- 

public:
    explicit Logger( const std::string& logFilename );
    virtual ~Logger();

    static void log( const std::string& text, LoggerType logType = TypeInfo );
    static void log( const std::ostringstream& text, LoggerType logType = TypeInfo );
    static void log( const char* text, LoggerType logType = TypeInfo );
};

} // namespace linx

#endif // !LOGGER_H

Logger.cpp

#include "Logger.h"
#include "TextFileWriter.h"

#include <iostream>
#include <iomanip>
#include <mutex>
// #include <shared_mutex>

namespace linx {

static Logger* s_pLogger = nullptr;

std::mutex  g_mutex;  // also removed the static storage qualifier.
//static std::shared_mutex s_mutex; // this was a wrong implementation

// White Text On Red Background
static const WORD WHITE_ON_RED = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED; 

Logger::Logger( const std::string& logFilename ) :
Singleton( TypeLogger ),
_logFilename( logFilename ),
_maxCharLength( 0 ),
_unknownLogType( "UNKNOWN" ) {
    // Order must match types defined in Logger::LoggerType enum
    _logTypes[0] = "Info";
    _logTypes[1] = "Warning";
    _logTypes[2] = "Error";
    _logTypes[3] = ""; // Console

    // Find widest log type string
    _maxCharLength = _unknownLogType.size();
    for ( const std::string& logType : _logTypes ) {
        if( _maxCharLength < logType.size() ) {
            _maxCharLength = logType.size();
        }
    }

    // this was wrong
    //std::shared_lock<std::shared_mutex> lock( s_mutex );

    { // scope for lock_guard
        std::lock_guard<std::mutex> lock( g_mutex );

        // Start Log File
        TextFileWriter file( _logFilename, false, false );

        // Prepare Console  - Windows Console (can substitute with your OS)
        _hConsoleOutput = GetStdHandle( STD_OUTPUT_HANDLE );

        CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
        GetConsoleScreenBufferInfo( _hConsoleOutput, &consoleInfo );
        _consoleDefualtColor = consoleInfo.wAttributes;
        // End - Windows Console specific

        s_pLogger = this;
    } // end scope: lock_guard must be destroyed here 

    // Must destroy lock_guard to unlock mutex before calling this function
    // this function is derived from Singleton but it calls Logger::log() static method which in turn uses the same mutex to lock.
    logMemoryAllocation( true );
}

Logger::~Logger() {
    logMemoryAllocation( false );
    s_pLogger = nullptr;
}

void Logger::log( const std::string& text, LoggerType logType ) {
    log( text.c_str(), logType );
}

void Logger::log( const std::ostringstream& text, LoggerType logType ) {
    log( text.str().c_str(), logType );
}

void Logger::log( const char* text, LoggerType logType ) {
    if( nullptr == s_pLogger ) {
        std::cout << "Logger has not been initialized, can not log " << text << '\n';
        return;
    }

    // this is wrong
    //std::shared_lock<std::shared_mutex> lock( s_mutex );

    std::lock_guard<std::mutex> lock( g_mutex );

    std::ostringstream stream;

    // Default White Text On Red Background
    WORD textColor = WHITE_ON_RED;

    // Choose log type text string, display "UNKNOWN" if logType is out of range
    stream << std::setfill( ' ' ) << std::setw( s_pLogger->_maxCharLength );

    try {
        if( TypeConsole != logType ) {
            stream << s_pLogger->_logTypes.at( logType );
        }
        if( TypeWarning == logType ) {
            // Yellow
            textColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN;
        } else if( TypeInfo == logType ) {
            // Green
            textColor = FOREGROUND_GREEN;
        } else if( TypeConsole == logType ) {
            // Cyan
            textColor = FOREGROUND_GREEN | FOREGROUND_BLUE;
        }
    } catch( ... ) {
        stream << s_pLogger->_unknownLogType;
    }

    // Date & Time
    if( TypeConsole != logType ) {
        SYSTEMTIME time;
        GetLocalTime( &time );

        stream << " [" << time.wYear << '.'
            << std::setfill( '0' ) << std::setw( 2 ) << time.wMonth << '.'
            << std::setfill( '0' ) << std::setw( 2 ) << time.wDay << ' '
            << std::setfill( ' ' ) << std::setw( 2 ) << time.wHour << ':'
            << std::setfill( '0' ) << std::setw( 2 ) << time.wMinute << ':'
            << std::setfill( '0' ) << std::setw( 2 ) << time.wSecond << '.'
            << std::setfill( '0' ) << std::setw( 3 ) << time.wMilliseconds << "] ";
    }
    stream << text << '\n';

    // Log Message
    SetConsoleTextAttribute( s_pLogger->_hConsoleOutput, textColor );
    std::cout << stream.str();

    // Save same message to file
    try {
        TextFileWriter file( s_pLogger->_logFilename, true, false );
        file.write( stream.str() );
    } catch( ... ) {
        // Ignore, not saved in log file
        std::cout << __FUNCTION__ << " failed to write to file: " << stream.str() << '\n';
    }

    // Reset to default color
    SetConsoleTextAttribute( s_pLogger->_hConsoleOutput, s_pLogger->_consoleDefualtColor );
}

} // namespace linx

Singleton.h

#ifndef SINGLETON_H
#define SINGLETON_H

namespace linx {

class Singleton {
public:
    enum SingletonType {
        TypeLogger = 0, // MUST BE FIRST!
    };

private:
    SingletonType _type;

public:
    Singleton( const Singleton& c ) = delete;
    Singleton& operator=( const Singleton& c ) = delete;
    virtual ~Singleton();

protected:
    explicit Singleton( SingletonType type );
    void logMemoryAllocation( bool isAllocated ) const;
};

} // namespace linx

#endif // !SINGLETON_H

Single.cpp

#include "Singleton.h"
#include "Logger.h"
#include "ExceptionHandler.h"

#include <string>
#include <array>

namespace linx {

struct SingletonInfo {
    const std::string singletonName;
    bool  isConstructed;

    SingletonInfo( const std::string& singletonNameIn ) :
        singletonName( singletonNameIn ),
        isConstructed( false ) {}
};

// Order must match types defined in Singleton::SingletonType
static std::array<SingletonInfo, 1> s_aSingletons = { SingletonInfo( "Logger" ) };

Singleton::Singleton( SingletonType type ) :
_type( type ) {
    bool saveInLog = s_aSingletons.at( TypeLogger ).isConstructed;

    try {
        if( !s_aSingletons.at( type ).isConstructed ) {
            // Test Initialize Order
            for( int i = 0; i < type; ++i ) {
                if( !s_aSingletons.at( i ).isConstructed ) {
                    throw ExceptionHandler( s_aSingletons.at( i ).singletonName +
                                            " must be constructed before constructing " +
                                            s_aSingletons.at( type ).singletonName,
                                            saveInLog );
                }
            }
            s_aSingletons.at( type ).isConstructed = true;
        } else {
            throw ExceptionHandler( s_aSingletons.at( type ).singletonName +
                                    " can only be constructed once.",
                                    saveInLog );
        }
    } catch( std::exception& ) {
        // type is out of range
        std::ostringstream stream;
        stream << __FUNCTION__ << " Invalid Singleton Type specified: " << type;
        throw ExceptionHandler( stream, saveInLog );
    }
}

Singleton::~Singleton() {
    s_aSingletons.at( _type ).isConstructed = false;
}

void Singleton::logMemoryAllocation( bool isAllocated ) const {
    if( isAllocated ) {
        Logger::log( "Created " + s_aSingletons.at( _type ).singletonName );
    }  else {
        Logger::log( "Destroyed " + s_aSingletons.at( _type ).singletonName );
    }   
}

} // namespace linx

TextFileWriter

#ifndef TEXT_FILE_WRITER_H
#define TEXT_FILE_WRITER_H

#include "FileHandler.h"

namespace linx {

class TextFileWriter : public FileHandler {
public:
    explicit TextFileWriter( const std::string& filename, bool appendToFile, bool saveExceptionInLog = true );
    virtual ~TextFileWriter() = default;

    void write( const std::string& str );

    TextFileWriter( const TextFileWriter& ) = delete;
    TextFileWriter& operator=( const TextFileWriter& ) = delete;
};

} // namespace linx

#endif // !TEXT_FILE_WRITER_H

TextFileWriter.cpp

#include "TextFileWriter.h"

namespace linx {

TextFileWriter::TextFileWriter( const std::string& filename, bool appendToFile, bool saveExceptionInLog ) :
    FileHandler( filename, saveExceptionInLog ) {
    _file.open( _filenameWithPath.c_str(),
                std::ios_base::out | (appendToFile ? std::ios_base::app : std::ios_base::trunc) );
    if( !_file.is_open() ) {
        throwError( __FUNCTION__ + std::string( " can not open file for writing" ) );
    }
}

void TextFileWriter::write( const std::string& str ) {
    _file << str;
}

} // namespace linx

FileHandler.h

#ifndef FILE_HANDLER_H
#define FILE_HANDLER_H

#include <string>
#include <sstream>
#include <fstream>

namespace linx {

class FileHandler {
private:
    bool _saveExceptionInLog;

protected:
    std::fstream _file;
    std::string  _filePath;
    std::string  _filenameWithPath;

public:
    virtual ~FileHandler();

    FileHandler( const FileHandler& c ) = delete;
    FileHandler& operator=( const FileHandler& c ) = delete;

protected:
    FileHandler( const std::string& filename, bool saveExceptionInLog );
    void throwError( const std::string& message ) const;
    void throwError( const std::ostringstream& message ) const;

    bool getString( std::string& str, bool appendPath );
}; 

} // namespace linx

#endif // !FILE_HANDLER_H

FileHandler.cpp

#include "ExceptionHandler.h"
#include "Logger.h"

namespace linx {

ExceptionHandler::ExceptionHandler( const std::string& message, bool saveInLog ) : 
_message( message ) {
    if( saveInLog ) {
        Logger::log( _message, Logger::TypeError );
    }
}

ExceptionHandler::ExceptionHandler( const std::ostringstream& streamMessage, bool saveInLog ) :
_message( streamMessage.str() ) {
    if( saveInLog ) {
        Logger::log( _message, Logger::TypeError );
    }
}

const std::string& ExceptionHandler::getMessage() const {
    return _message;
}

} // namespace linx

【讨论】:

  • 上述程序有错误。在我的记录器类中,与正在使用的 shared_lock 和 shared_mutex 有关。您可以参考类和 cmets 了解所做的更改。
猜你喜欢
  • 2020-06-26
  • 1970-01-01
  • 2012-03-29
  • 2013-08-31
  • 1970-01-01
  • 1970-01-01
  • 2021-09-17
  • 2011-07-02
  • 1970-01-01
相关资源
最近更新 更多