How should I write an efficient exception class to show the error that can be prevented by fixing the source code mistakes before run-time?
This is the reason I chose std::invalid_argument
.
My exception class(not working, obviously):
class Foo_Exception : public std::invalid_argument
{
private:
std::string Exception_Msg;
public:
explicit Foo_Exception( const std::string& what_arg );
virtual const char* what( ) const throw( );
};
explicit Foo_Exception::Foo_Exception( const std::string& what_arg ) // how should I write this function???
{
Exception_Msg.reserve( 130000 );
Exception_Msg = "A string literal to be appended, ";
Exception_Msg = std::to_string( /* a constexpr int */ );
Exception_Msg = /* a char from a const unordered_set<char> */;
}
const char* Foo_Exception::what( ) const throw( )
{
return Exception_Msg.c_str( );
}
An if
block that throws Foo_Exception
:
void setCharacter( const char& c )
{
if ( /* an invalid character (c) is passed to setCharacter */ )
{
try
{
const Foo_Exception foo_exc;
throw foo_exc;
}
catch( const Foo_Exception& e )
{
std::cerr << e.what( );
return;
}
}
/*
rest of the code
*/
}
int main( )
{
setCharacter( '-' ); // dash is not acceptable!
return 0;
}
As you can see, I need to concatenate Exception_Msg
with a few substrings in order to form the completed message. These substrings are not only string literals but also constexpr int
s and char
s from a static std::unordered_set<char>
. That's why I used std::string because it has the string::operator =
which is easy to work with. And needless to say, my goal is to reduce heap allocations down to only 1.
Another very important question is that where should I place the handler( the try-catch )? Inside the main()
wrapping the setCharacter()
or keep it inside setCharacter
?
Please make a good and standard custom exception class similar to the above. Or write your own. Thanks in advance.
CodePudding user response:
Rather than new char[]
every time, why not have a std::string
data member?
class Foo_Exception : public std::exception /* ? */
{
std::string msg;
public:
Foo_Exception() {
msg.reserve( 130000 );
msg = "A string literal to be appended, ";
msg = "another string, ";
msg = "yet another one";
}
const char * what() const override { return msg.data(); }
/* other members ? */
}
Or you could derive from one of the std
exception types that takes a string as a parameter on construction
class Foo_Exception : public std::runtime_error /* or logic_error */
{
public:
Foo_Exception() : runtime_error("A string literal to be appended, " "another string, " "yet another one") {}
/* other members ? */
}
However if you just have string literals, you can split them on multiple source lines, and have zero allocations.
const char * what() const override {
return "A string literal to be appended, "
"another string, "
"yet another one";
}
CodePudding user response:
I solved this problem based on the feedback that I received from others through comments and answers. So I decided to leave my own answer/solution here for future readers.
Below can be seen what I came up with after much thought and research. This solution is fairly simple and readable.
Here is the exception class interface:
Foo_Exception.h
#include <exception>
class Foo_Exception : public std::invalid_argument
{
public:
explicit Foo_Exception( const std::string& what_arg );
};
And here is its implementation:
Foo_Exception.cpp
#include "Foo_Exception.h"
Foo_Exception::Foo_Exception( const std::string& what_arg )
: std::invalid_argument( what_arg )
{
}
A function that throws Foo_Exception
:
Bar.cpp
#include "Bar.h"
#include "Foo_Exception.h"
void setCharacter( const char& c )
{
if ( /* an invalid character (c) is passed to setCharacter */ )
{
std::string exceptionMsg;
exceptionMsg.reserve( 130000 );
exceptionMsg = "A string literal to be appended, ";
exceptionMsg = std::to_string( /* a constexpr int */ );
exceptionMsg = /* a char from a const unordered_set<char> */;
throw Foo_Exception( exceptionMsg );
}
/*
rest of the code
*/
}
How to handle the exception:
main.cpp
#include <iostream>
#include "Bar.h"
#include "Foo_Exception.h"
int main( )
{
try
{
setCharacter( '-' ); // The dash character is not valid! Throws Foo_Exception.
}
catch ( const Foo_Exception& e )
{
std::cerr << e.what( ) << '\n';
}
return 0;
}
Summary of the Changes:
Notice how there's no need for a
what()
function since the compiler generates it implicitly becauseFoo_Exception
inherits fromstd::invalid_argument
.Also, the process of creating the exception message was moved from the
Foo_Exception
's ctor to the body of the functionsetCharacter
which actually throws the aforementioned exception. This way, theFoo_Exception
's ctor is not responsible for creating the message. Instead, it is created in the body of the function that throws and is then passed to the ctor ofFoo_Exception
to initialize the new exception object.The data member
std::string Exception_Msg
was also removed fromFoo_Exception
as it wasn't needed anymore.Finally, the try-catch block was moved to
main()
so that it now wraps aroundsetCharacter()
and catches aFoo_Exception
object that it might throw.
Final word: Any suggestions to further improve my answer is highly appreciated. Thanks a ton for all the feedback.