在 C++ 中引发异常和处理某些异常类型的正确方法是什么

What is the proper way to raise exception and handle certain exception type in C++

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

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

例如:

我目前正在实施数据库客户端。如果查询要求一个错误的 table 或一个错误的列,我会在低级驱动程序中抛出异常,如下所示:

throw MySQLException("message");

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

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

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

Link 教程将是我们的。

您可以有多个 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块。如果 none 匹配并且没有 ... catch-all,则异常将从 try/catch 块传播出去。可以找到 here:

对确切 try/catch 行为的更精确解释

When an exception of type E is thrown by any statement in compound-statement, it is matched against the types of the formal parameters T of each catch-clause in handler-seq, in the order in which the catch clauses are listed. The exception is a match if any of the following is true:

  • E and T are the same type (ignoring top-level cv-qualifiers on T)
  • T is an lvalue-reference to (possibly cv-qualified) E
  • T is an unambiguous public base class of E
  • T is a reference to an unambiguous public base class of E
  • T is (possibly cv-qualified) U or const U& (since C++14), and U is a pointer or pointer to member (since C++17) type, and E is also a pointer or pointer to member (since C++17) type that is implicitly convertible to U by one or more of
    • a standard pointer conversion other than one to a private, protected, or ambiguous base class
    • a qualification conversion
    • a function pointer conversion (since C++17)
  • T is a pointer or a pointer to member or a reference to a const pointer (since C++14), while E is std::nullptr_t.

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

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

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

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


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

  • ExceptionHandler - It uses the Logger class and is used to handle messages that are thrown and or sent to the Logger.
  • Logger - A derived class from Singleton. It logs to either the console, a text file or both. It uses the TextFileWriter to write to a text file.
  • Singleton - A base class that all Singleton Type classes are derived from.
  • TextFileWriter - A derived class from FileHandler. It writes to a text file.
  • FileHandler - A base class that all file handling type classes derive from.


使用上面的示例程序类:

#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