release 0.91
This commit is contained in:
686
mrw/arg.hpp
Normal file
686
mrw/arg.hpp
Normal file
@@ -0,0 +1,686 @@
|
||||
#include <mrw/exception.hpp>
|
||||
#include <mrw/stacktrace.hpp>
|
||||
#include <mrw/smartpointer.hpp>
|
||||
#include <mrw/simpletrace.hpp>
|
||||
#include <stdlib.h> // exit
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace mrw {
|
||||
|
||||
/** @defgroup arguments C++ Evaluation of Command Line Arguments
|
||||
|
||||
@brief These classes do simple and easy command line argment evaluation
|
||||
in C++.
|
||||
|
||||
Features:
|
||||
- every argument has a long and a short option
|
||||
- all arguments are optional and provide a default value
|
||||
- the order of options is not important
|
||||
- every option can take any (fixed) number of additional parameter of
|
||||
type
|
||||
- string
|
||||
- integer
|
||||
- boolean (@c "yes", @c "on", @c "true" evaluates to @c true)
|
||||
- short options can be combined, instead of
|
||||
@c -a @c -b @c -c @c 15 you can simply write @c -abc @c 15
|
||||
- automated help display (support for option @c -h)
|
||||
|
||||
@c mrw::Args is the main user interface class that represents
|
||||
all command line options with their arguments. It is implemented
|
||||
as singleton, so the same instance can be accessed from
|
||||
everywhere in the code. It mst be setup just in the beginning of
|
||||
the @c main() function.
|
||||
|
||||
The other important class for the end user is @c mrw::Opt, one
|
||||
possible option with additional parameter. The end user needs @c
|
||||
mrw::Opt to setup all allowed command line options in the
|
||||
beginning, bevore evaluation of the user given command line is
|
||||
done (before @c argc and @c argv is shifted into @c mrw::Args.
|
||||
|
||||
The third class a user should know is @c mrw::Param. It
|
||||
represents the arguments to one option. Every instance of @c
|
||||
mrw::Opt owns one instance of @c mrw::Param that is either empty
|
||||
or list of (mandatory) arguments of type @c std::string, @c int
|
||||
or @c bool.
|
||||
|
||||
The classes are normally used this way:
|
||||
|
||||
@code
|
||||
// this program may be called e.g. with the following arguments:
|
||||
// ./a.out --coordinates 13 1 -vo out.txt -n MyName
|
||||
int main(int argv, const char * const * const argv) {
|
||||
try {
|
||||
mrw::Args::instance()
|
||||
// setup the possible options
|
||||
<<mrw::Opt('h', "--help", "Show this help text")
|
||||
<<mrw::Opt('v', "--verbose", "print more information")
|
||||
<<mrw::Opt('q', "--quiet", "be quiet")
|
||||
<<mrw::Opt('n', "--name", mrw::Param()<<"MRW", "name of the user")
|
||||
<<mrw::Opt('o', "--output-file", mrw::Param()<<"", "file to load")
|
||||
<<mrw::Opt('c', "--coordinates", mrw::Param()<<0<<0, "X, Y coordinate")
|
||||
// set a description text for help
|
||||
<<"This is a testprogram for argument evaluation in C++"
|
||||
// define the help option
|
||||
<<'h'
|
||||
// shift in the command line arguments
|
||||
<<argc<<argv;
|
||||
...
|
||||
// example usage of simple option
|
||||
if (mrw::Args::instance().find('v')) // be verbose here
|
||||
...
|
||||
// example usage of option with (one) parameter
|
||||
ifstream file(mrw::Args::instance().find('o').toString().c_str());
|
||||
...
|
||||
return 0
|
||||
}
|
||||
} catch (mrw::exception& x) {
|
||||
// trace error, print help or mention option -h
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
//@{
|
||||
|
||||
/** @brief List of additional (mandatory) parameter to one command
|
||||
line argument.
|
||||
@pre #include<mrw/arg.hpp>
|
||||
|
||||
A new mandatory parameter is added to the list of parameter, by
|
||||
shifting the default value into the instance of @c
|
||||
mrw::Param. E.g. add a string, that defaults to @c "noname", an
|
||||
integer, that defaults to @c 4, another integer that defaults to
|
||||
@2 and a boolean that defaults to @c "true":
|
||||
|
||||
@code
|
||||
// if you need the instance as variable:
|
||||
mrw::Param p();
|
||||
p<<"noname"<<4<<2<<true;
|
||||
// or in an expression:
|
||||
mrw::Opt o('e', "--example", mrw::Param()<<"noname"<<4<<2<<true, "");
|
||||
@endcode
|
||||
|
||||
To access a value at a given position, simply use @c
|
||||
operator[]. Then use @c mrw::Value::toString, @c
|
||||
mrw::Value::toInt or @c mrw::Value::toBool to get the value of
|
||||
that parameter. Of course yo must know the correct type of a
|
||||
parameter at a given position, but since you are the programmer
|
||||
you know it, or you can get it by running your program with the
|
||||
help option, mostly @c -h. To retrieve the parameters setup in
|
||||
the example above (connected to option @c -e or @c --example),
|
||||
either the default value, or the value overwritten by the user,
|
||||
simply type:
|
||||
|
||||
@code
|
||||
mrw::Args& args = mrw::Args::instance();
|
||||
std::string theString = args[0]->toString();
|
||||
int firstInteger = args[1]->toInt();
|
||||
int secondInteger = args[2]->toInt();
|
||||
bool theBoolean = args[3]->toBool();
|
||||
@endcode
|
||||
|
||||
@section argParts Setup Command Line from Different Program Parts
|
||||
|
||||
If your software is large and splitted into different parts (or
|
||||
sub projects or modules, ...), all with their own parameter, you
|
||||
can use the following trick: Statical variables are initialized
|
||||
before the @c main() function is called.
|
||||
|
||||
In part Abc write in a code file (not in a header file):
|
||||
|
||||
@code
|
||||
class AbcArguments {
|
||||
public:
|
||||
AbcArguments() {
|
||||
mrw::Args::instance()
|
||||
<<mrw::Opt('n', "--name", mrw::Param()<<"MRW", "name of the user")
|
||||
<<mrw::Opt('o', "--output-file", mrw::Param()<<"", "file to load")
|
||||
<<mrw::Opt('c', "--coordinates", mrw::Param()<<0<<0,
|
||||
"X, Y coordinate")
|
||||
<<"Description text for part Abc, will be added to the\n"
|
||||
"overall documentation";
|
||||
}
|
||||
};
|
||||
static AbcArgument abcArgumentInitializer;
|
||||
@endcode
|
||||
|
||||
Do the same for all other parts Then the @c main() function reduces to:
|
||||
|
||||
@code
|
||||
int main(int argc, const char * const * const argv) {
|
||||
// set the help and evaluate the user given arguments
|
||||
mrw::Args::instance()
|
||||
<<mrw::Opt('h', "--help", "Show this help text")
|
||||
<<'h'<<argc<<argv;
|
||||
...
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
class Param {
|
||||
|
||||
public:
|
||||
|
||||
/** @brief Abstract base class to represent one single parameter value.
|
||||
@pre #include<mrw/arg.hpp>
|
||||
*/
|
||||
class Value {
|
||||
|
||||
public:
|
||||
|
||||
virtual ~Value() {}
|
||||
|
||||
/** @brief If the instance is a @c std::string, return that
|
||||
string, otherwise throw an exception.
|
||||
@throw mrw::bad_cast if the instance is not a string
|
||||
@return the string, if the instance is a string
|
||||
*/
|
||||
virtual const std::string& toString() const throw(mrw::exception) {
|
||||
throw mrw::bad_cast();
|
||||
}
|
||||
|
||||
/** @brief If the instance is an @c int, return that integer,
|
||||
otherwise throw an exception.
|
||||
@throw mrw::bad_cast if the instance is not a integer
|
||||
@return the integer, if the instance is a integer
|
||||
*/
|
||||
virtual int toInt() const throw(mrw::exception) {
|
||||
throw mrw::bad_cast();
|
||||
}
|
||||
|
||||
/** @brief If the instance is an @c bool, return that boolean,
|
||||
otherwise throw an exception.
|
||||
@note the following typings are converted to @c true:
|
||||
- true
|
||||
- yes
|
||||
- on
|
||||
Everything else is converted to @c false.
|
||||
@throw mrw::bad_cast if the instance is not a boolean
|
||||
@return the boolean, if the instance is a boolean
|
||||
*/
|
||||
virtual bool toBool() const throw(mrw::exception) {
|
||||
throw mrw::bad_cast();
|
||||
}
|
||||
|
||||
/// @brief returns a printable representation of the value
|
||||
virtual std::string printable() const throw(mrw::bad_exception) = 0;
|
||||
|
||||
/// @brief returns a printable typename of the value
|
||||
virtual const std::string& typestr() const throw(mrw::bad_exception)=0;
|
||||
|
||||
protected:
|
||||
friend class Args; // allow assign for Param
|
||||
virtual void operator=(const std::string&) throw(mrw::exception) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
class StringValue: public Value {
|
||||
public:
|
||||
virtual ~StringValue() {}
|
||||
StringValue(const std::string& s) throw(mrw::bad_exception): _s(s) {
|
||||
}
|
||||
virtual const std::string& toString() const throw(mrw::exception) {
|
||||
return _s;
|
||||
}
|
||||
virtual const std::string& typestr() const throw(mrw::bad_exception) {
|
||||
static std::string name("string");
|
||||
return name;
|
||||
}
|
||||
virtual std::string printable() const throw(mrw::bad_exception) {
|
||||
return _s;
|
||||
}
|
||||
protected:
|
||||
virtual void operator=(const std::string& s) throw(mrw::exception) {
|
||||
_s = s;
|
||||
}
|
||||
private:
|
||||
std::string _s;
|
||||
};
|
||||
|
||||
class IntValue: public Value {
|
||||
public:
|
||||
virtual ~IntValue() {}
|
||||
IntValue(int i) throw(mrw::bad_exception): _i(i) {
|
||||
}
|
||||
virtual int toInt() const throw(mrw::exception) {
|
||||
return _i;
|
||||
}
|
||||
virtual const std::string& typestr() const throw(mrw::bad_exception) {
|
||||
static std::string name("integer");
|
||||
return name;
|
||||
}
|
||||
virtual std::string printable() const throw(mrw::bad_exception) {
|
||||
return ((std::stringstream&)(std::stringstream()<<_i)).str();
|
||||
}
|
||||
protected:
|
||||
virtual void operator=(const std::string& s) throw(mrw::exception) {
|
||||
if (!(std::stringstream(s)>>_i)) throw mrw::bad_cast();
|
||||
}
|
||||
private:
|
||||
int _i;
|
||||
};
|
||||
|
||||
class BoolValue: public Value {
|
||||
public:
|
||||
virtual ~BoolValue() {}
|
||||
BoolValue(bool b) throw(mrw::bad_exception): _b(b) {
|
||||
}
|
||||
virtual bool toBool() const throw(mrw::exception) {
|
||||
return _b;
|
||||
}
|
||||
virtual const std::string& typestr() const throw(mrw::bad_exception) {
|
||||
static std::string name("boolean (\"yes\" or \"no\")");
|
||||
return name;
|
||||
}
|
||||
virtual std::string printable() const throw(mrw::bad_exception) {
|
||||
return _b?"yes":"no";
|
||||
}
|
||||
protected:
|
||||
virtual void operator=(const std::string& s) throw(mrw::exception) {
|
||||
_b = s=="true" || s=="yes" || s=="on";
|
||||
}
|
||||
private:
|
||||
bool _b;
|
||||
};
|
||||
|
||||
typedef std::vector< mrw::SmartPointer<Value> > Params;
|
||||
Params _params;
|
||||
|
||||
public:
|
||||
|
||||
/// @brief returns the number of (mandatory) parameter
|
||||
int size() const throw(std::bad_exception) {
|
||||
return _params.size();
|
||||
}
|
||||
|
||||
/// @brief add one more mandatory string parameter
|
||||
Param& operator<<(const char* const s) throw(mrw::bad_exception) {
|
||||
_params.push_back(new StringValue(s));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief add one more mandatory string parameter
|
||||
Param& operator<<(const std::string& s) throw(mrw::bad_exception) {
|
||||
_params.push_back(new StringValue(s));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// @brief add one more mandatory integer parameter
|
||||
Param& operator<<(int i) throw(mrw::bad_exception) {
|
||||
_params.push_back(new IntValue(i));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// @brief add one more mandatory boolean parameter
|
||||
Param& operator<<(bool b) throw(mrw::bad_exception) {
|
||||
_params.push_back(new BoolValue(b));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** @brief get parameter number @i
|
||||
@throw mrw::out_of_range if @c i is too big */
|
||||
const mrw::SmartPointer<Value>& operator[](unsigned int i) const
|
||||
throw(mrw::exception) {
|
||||
if (i<_params.size()) return _params[i];
|
||||
throw mrw::out_of_range
|
||||
(((std::stringstream&)
|
||||
(std::stringstream()<<"check failed: "
|
||||
<<i<<'<'<<_params.size())).str());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
friend class Args; // allow set
|
||||
mrw::SmartPointer<Value>& setable(unsigned int i)
|
||||
throw(mrw::exception) {
|
||||
if (i<_params.size()) return _params[i];
|
||||
throw mrw::out_of_range
|
||||
(((std::stringstream&)
|
||||
(std::stringstream()<<"check failed: "
|
||||
<<i<<'<'<<_params.size())).str());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/** @brief this class represents one command line option
|
||||
@pre #include<mrw/arg.hpp>
|
||||
|
||||
The library user needs this class when setting up the list of
|
||||
supported command line ooptions: Simply shift one instance of @c
|
||||
mrw::Opt per supported command line option into @c
|
||||
mrw::Args::instance(), e.g.:
|
||||
|
||||
@code
|
||||
mrw::Args::instance()
|
||||
<<mrw::Opt('h', "--help", "Show this help text");
|
||||
@endcode
|
||||
*/
|
||||
class Opt {
|
||||
|
||||
public:
|
||||
|
||||
/** @brief create an @c mrw::Opt with additional parameter
|
||||
@param shortname short name of the option
|
||||
@param longname long name of the option, must start with "--"
|
||||
@param param the additional parameter
|
||||
@param help the help string for this option
|
||||
*/
|
||||
Opt::Opt(const char shortname, const std::string& longname,
|
||||
const Param& param, const std::string& help)
|
||||
throw(mrw::bad_exception):
|
||||
_set(false), _shortname(shortname), _longname(longname),
|
||||
_param(param), _help(help) {
|
||||
}
|
||||
|
||||
/** @brief create a simple @c mrw::Opt
|
||||
|
||||
This option is either set or not set, there are no additional
|
||||
parameter.
|
||||
|
||||
@param shortname short name of the option
|
||||
@param longname long name of the option, must start with "--"
|
||||
@param help the help string for this option
|
||||
*/
|
||||
Opt::Opt(const char shortname, const std::string& longname,
|
||||
const std::string& help)
|
||||
throw(mrw::bad_exception):
|
||||
_set(false), _shortname(shortname), _longname(longname),
|
||||
_help(help) {
|
||||
}
|
||||
|
||||
/** @brief get the help text for this option */
|
||||
const std::string& help() const throw(mrw::bad_exception) {
|
||||
return _help;
|
||||
}
|
||||
|
||||
/** @brief find out, whether this option was set by the user
|
||||
|
||||
Example: Check whether the user has set the @c -v option for
|
||||
verbose output:
|
||||
|
||||
@code
|
||||
if (mrw::Args::instance().find('v')) // -v is set
|
||||
@endcode
|
||||
|
||||
@return
|
||||
- @c true if the user has started the program with this option
|
||||
- @c false if the user has not set this option
|
||||
*/
|
||||
operator bool() const throw(std::bad_exception) {return _set;}
|
||||
|
||||
/** @brief get one of the additional parameter
|
||||
|
||||
If this option has additional parameter, get the @c i-th of them.
|
||||
|
||||
@throw mrw::out_of_range if @c i is too big
|
||||
@param i number of the additional parameter to get (starting with @c 0)
|
||||
@return a smart pointer to the value (default or given by the user)
|
||||
*/
|
||||
const mrw::SmartPointer<Param::Value>& operator[](unsigned int i) const
|
||||
throw(mrw::exception) {
|
||||
return _param[i];
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
friend class Args; // is allowed to set values
|
||||
void set() const throw(mrw::bad_exception) {
|
||||
_set = true;
|
||||
}
|
||||
Param& args() const throw(mrw::bad_exception) {
|
||||
return _param;
|
||||
}
|
||||
mutable bool _set;
|
||||
char _shortname;
|
||||
std::string _longname;
|
||||
mutable Param _param;
|
||||
std::string _help;
|
||||
};
|
||||
|
||||
/** @brief handle command line arguments
|
||||
@pre #include<mrw/arg.hpp>
|
||||
|
||||
This class handles command line arguments. It is a
|
||||
singleton. Get the one and only instance of this class with @c
|
||||
mrw::Args::instance(). It is setup by shifting values into this
|
||||
class. The order is important, be sure that you shift in @c argc
|
||||
and @c argv as last parameters.
|
||||
|
||||
Example setup:
|
||||
|
||||
@code
|
||||
mrw::Args::instance()
|
||||
<<mrw::Opt('h', "--help", "Show this help text")
|
||||
<<mrw::Opt('v', "--verbose", "print more information")
|
||||
<<"This is a testprogram for argument evaluation in C++"
|
||||
<<'h'<<argc<<argv;
|
||||
@endcode
|
||||
*/
|
||||
class Args {
|
||||
|
||||
public:
|
||||
|
||||
typedef std::list<std::string> OtherArgs;
|
||||
|
||||
/// @brief get the one and only instance
|
||||
static Args& instance() throw(mrw::bad_exception) { // singleton
|
||||
static Args _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
/** @brief setup an acceptable option
|
||||
|
||||
Setup an acceptable user option.
|
||||
|
||||
Example:
|
||||
|
||||
@code
|
||||
mrw::Args::instance()
|
||||
<<mrw::Opt('v', "--verbose", "print more information");
|
||||
@endcode
|
||||
*/
|
||||
Args& operator<<(const mrw::Opt& opt) throw(mrw::invalid_argument) {
|
||||
// twice the same, but other sort order
|
||||
Options::iterator it(_options.insert(_options.end(), opt));
|
||||
if (!_shortopts.insert(ShortOpts::value_type(opt._shortname, it)).second)
|
||||
throw mrw::invalid_argument(std::string(1, opt._shortname));
|
||||
if (!_longopts.insert(LongOpts::value_type(opt._longname, it)).second)
|
||||
throw mrw::invalid_argument(opt._longname);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** @brief setup the number of arguments
|
||||
|
||||
Setup the number of arguments.
|
||||
This must be done before @c argv is shifted in.
|
||||
|
||||
Example:
|
||||
|
||||
@code
|
||||
int main(int argv, const char * const * const argv) {
|
||||
mrw::Args::instance()<<argc<<argv;
|
||||
...
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
Args& operator<<(int argc) throw(mrw::bad_exception) {
|
||||
_argc = argc;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** @brief setup the C array of command line arguments
|
||||
|
||||
Setup the C array of command line arguments. This must be the
|
||||
very last thing shifted in.
|
||||
|
||||
Example:
|
||||
|
||||
@code
|
||||
int main(int argv, const char * const * const argv) {
|
||||
mrw::Args::instance()<<argc<<argv;
|
||||
...
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
Args& operator<<(const char *const*const argv) throw(mrw::exception) {
|
||||
if (_argc<0)
|
||||
throw mrw::invalid_argument("argc was not set when shifting argv");
|
||||
return parse(_argc, argv);
|
||||
}
|
||||
|
||||
/** @brief add a description text
|
||||
|
||||
Add a description text. This description text is shown in the
|
||||
@c DESCRIPTION section of the help display. If the description
|
||||
text is shifted in more then once, the different sections are
|
||||
appended with new line and an empty line between.
|
||||
|
||||
Example:
|
||||
|
||||
@code
|
||||
mrw::Args::instance()<<"this is a description for --help";
|
||||
@endcode
|
||||
*/
|
||||
Args& operator<<(const std::string& description) throw(mrw::exception) {
|
||||
if (_description=="")
|
||||
_description = description;
|
||||
else
|
||||
_description += "\n\n"+description;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** @brief set the help option
|
||||
|
||||
Define which option prints the help text. There is no code
|
||||
needed for printing the help text: if the help option has been
|
||||
shifted in, help is printed automatically at user request,
|
||||
then the program is terminated. Only specify the short option
|
||||
name, the long option name is known.
|
||||
|
||||
Example:
|
||||
|
||||
@code
|
||||
mrw::Args::instance()<<'h';
|
||||
@endcode
|
||||
*/
|
||||
Args& operator<<(char help) throw(mrw::exception) {
|
||||
_help = help;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** @brief get an option, given the short option name
|
||||
@throw mrw::out_of_range if the option does not exist
|
||||
*/
|
||||
const Opt& find(char c) const throw(mrw::exception) {
|
||||
ShortOpts::const_iterator it(_shortopts.find(c));
|
||||
if (it==_shortopts.end()) throw mrw::out_of_range(std::string(1, c));
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
/** @brief get an option, given the long option name
|
||||
@throw mrw::out_of_range if the option does not exist
|
||||
*/
|
||||
const Opt& find(const std::string& s) const throw(mrw::exception) {
|
||||
LongOpts::const_iterator it(_longopts.find(s));
|
||||
if (it==_longopts.end()) throw mrw::out_of_range(s);
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
/** @brief get all non interpreted options
|
||||
|
||||
All user options that don't fit the defined and interpreted
|
||||
options. The meaning for this is, that a user may append,
|
||||
e.g. a list of file names.
|
||||
*/
|
||||
const OtherArgs& otherArgs() {
|
||||
return _otherargs;
|
||||
}
|
||||
|
||||
/** @brief get the file name of the executable, that's @c argv[0] */
|
||||
const std::string& filename() throw(mrw::bad_exception) {
|
||||
return _filename;
|
||||
}
|
||||
|
||||
/** @brief print the help text, then exit */
|
||||
void help() {
|
||||
std::cout<<"USAGE: "<<std::endl
|
||||
<<" "<<_filename<<" [ OPTIONS ]"<<std::endl
|
||||
<<"OPTIONS:"<<std::endl;
|
||||
for (Options::iterator it(_options.begin()); it!=_options.end(); ++it) {
|
||||
std::cout<<" -"<<it->_shortname<<" | "<<it->_longname;
|
||||
for (int i(0); i<it->_param.size(); ++i)
|
||||
std::cout<<" <"<<(*it)[i]->typestr()<<">";
|
||||
if (it->_param.size()>0) std::cout<<" (default: ";
|
||||
for (int i(0); i<it->_param.size()-1; ++i)
|
||||
std::cout<<(*it)[i]->printable()<<" ";
|
||||
if (it->_param.size()>0)
|
||||
std::cout<<(*it)[it->_param.size()-1]->printable()<<")";
|
||||
std::cout<<std::endl<<" "<<it->help()<<std::endl;
|
||||
}
|
||||
if (_description.size()>0)
|
||||
std::cout<<"DESCRIPTION:"<<std::endl
|
||||
<<_description<<std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
private:
|
||||
Args& parse(int argc, const char*const*const argv) throw(mrw::exception) {
|
||||
if (argc>0) _filename = argv[0];
|
||||
for (int i(1); i<argc; ++i) {
|
||||
std::string arg(argv[i]);
|
||||
if (arg.find("--")==0 && arg!="--") { // long arguments
|
||||
LongOpts::iterator it(_longopts.find(arg));
|
||||
if (it!=_longopts.end() || i+it->second->args().size()>=argc)
|
||||
throw mrw::invalid_argument(arg);
|
||||
it->second->set();
|
||||
for (int j(0), l(it->second->args().size()); j<l; ++j) {
|
||||
*(it->second->args().setable(j)) = argv[++i];
|
||||
}
|
||||
} else if (arg.find("-")==0) { // short arguments
|
||||
// first check all, then set all
|
||||
for (int j(1), l(arg.size()); j<l; ++j) {
|
||||
ShortOpts::iterator it(_shortopts.find(arg[j]));
|
||||
if (it==_shortopts.end() || it->second->args().size()>0 &&
|
||||
(j+1!=l || i+it->second->args().size()>=argc))
|
||||
throw mrw::invalid_argument(arg);
|
||||
}
|
||||
for (int j(1), l(arg.size()); j<l; ++j) {
|
||||
ShortOpts::iterator it(_shortopts.find(arg[j]));
|
||||
it->second->set();
|
||||
if (j+1==l && it->second->args().size()>0) {
|
||||
for (int k(0); k < it->second->args().size(); ++k) {
|
||||
*(it->second->args().setable(k)) = argv[++i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (arg!="--") _otherargs.push_back(arg);
|
||||
while (++i<argc) _otherargs.push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
if (_help && find(_help)) help();
|
||||
return *this;
|
||||
}
|
||||
typedef std::list<Opt> Options;
|
||||
typedef std::map<char, Options::iterator> ShortOpts;
|
||||
typedef std::map<std::string, Options::iterator> LongOpts;
|
||||
Args(): _argc(-1), _help(0) {} // singleton
|
||||
Args& operator=(const Args&); // singleton, not implemented
|
||||
std::string _filename;
|
||||
Options _options;
|
||||
ShortOpts _shortopts;
|
||||
LongOpts _longopts;
|
||||
OtherArgs _otherargs;
|
||||
int _argc;
|
||||
char _help;
|
||||
std::string _description;
|
||||
};
|
||||
|
||||
//@}
|
||||
}
|
@@ -42,5 +42,3 @@ int main() {
|
||||
runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
|
||||
return runner.run() ? 0 : 1;
|
||||
}
|
||||
|
||||
static char* c = new char[100];
|
||||
|
@@ -96,7 +96,7 @@ HIDE_UNDOC_CLASSES = NO
|
||||
# If set to NO (the default) these declarations will be included in the
|
||||
# documentation.
|
||||
|
||||
HIDE_FRIEND_COMPOUNDS = NO
|
||||
HIDE_FRIEND_COMPOUNDS = YES
|
||||
|
||||
# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
|
||||
# documentation blocks found inside the body of a function.
|
||||
|
25
mrw/examples/smartpointer.cpp
Normal file
25
mrw/examples/smartpointer.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <mrw/smartpointer.hpp>
|
||||
|
||||
class A {
|
||||
public: int a;
|
||||
protected: virtual void fn() {} // dynamic_cast requires a virtual function
|
||||
};
|
||||
|
||||
class B: virtual public A {
|
||||
public: int b;
|
||||
};
|
||||
|
||||
int main(int, char**) {
|
||||
utl::SmartPointer<int> i1 = new int;
|
||||
*i1 = 13;
|
||||
utl::SmartPointer<int> i2 = i1;
|
||||
utl::SmartPointer<int> i3;
|
||||
if (!i3) i3 = i2;
|
||||
*i2 = 666; // *i1 is now 666 too
|
||||
utl::SmartPointer<A> b1 = new B;
|
||||
utl::SmartPointer<B> b2 = b1; // b1 and b2 point to the same instance
|
||||
b1->a = 0; // b1->b does not compile
|
||||
b2->a = 1;
|
||||
b2->b = 2;
|
||||
return 0;
|
||||
} // memory is automatically freed
|
@@ -2,6 +2,8 @@
|
||||
#define __MRW_EXCEPTION_HPP__
|
||||
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
#include <string>
|
||||
|
||||
namespace mrw {
|
||||
@@ -167,11 +169,140 @@ call of fn0 successful
|
||||
public:
|
||||
exception() throw(std::bad_exception);
|
||||
virtual ~exception() throw();
|
||||
virtual const char* what() const throw() {
|
||||
return std::exception::what();
|
||||
}
|
||||
const std::string& stacktrace() const throw(std::bad_exception);
|
||||
private:
|
||||
StackTrace* _stacktrace;
|
||||
};
|
||||
|
||||
/// Replacement for @c std::bad_alloc, but with stack trace
|
||||
class bad_alloc:
|
||||
virtual public mrw::exception, virtual public std::bad_alloc {
|
||||
public:
|
||||
virtual const char* what() const throw() {
|
||||
return std::bad_alloc::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::bad_cast, but with stack trace
|
||||
class bad_cast:
|
||||
virtual public mrw::exception, virtual public std::bad_cast {
|
||||
public:
|
||||
virtual const char* what() const throw() {
|
||||
return std::bad_cast::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::bad_exception, but with stack trace
|
||||
class bad_exception:
|
||||
virtual public mrw::exception, virtual public std::bad_exception {
|
||||
public:
|
||||
virtual const char* what() const throw() {
|
||||
return std::bad_exception::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::bad_typeid, but with stack trace
|
||||
class bad_typeid:
|
||||
virtual public mrw::exception, virtual public std::bad_typeid {
|
||||
public:
|
||||
virtual const char* what() const throw() {
|
||||
return std::bad_typeid::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::logic_error, but with stack trace
|
||||
class logic_error:
|
||||
virtual public mrw::exception, virtual public std::logic_error {
|
||||
public:
|
||||
logic_error(const std::string& arg): std::logic_error(arg) {}
|
||||
virtual const char* what() const throw() {
|
||||
return std::logic_error::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::domain_error, but with stack trace
|
||||
class domain_error:
|
||||
virtual public mrw::exception, virtual public std::domain_error {
|
||||
public:
|
||||
domain_error(const std::string& arg): std::domain_error(arg) {}
|
||||
virtual const char* what() const throw() {
|
||||
return std::domain_error::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::invalid_argument, but with stack trace
|
||||
class invalid_argument:
|
||||
virtual public mrw::exception, virtual public std::invalid_argument {
|
||||
public:
|
||||
invalid_argument(const std::string& arg): std::invalid_argument(arg) {}
|
||||
virtual const char* what() const throw() {
|
||||
return std::invalid_argument::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::length_error, but with stack trace
|
||||
class length_error:
|
||||
virtual public mrw::exception, virtual public std::length_error {
|
||||
public:
|
||||
length_error(const std::string& arg): std::length_error(arg) {}
|
||||
virtual const char* what() const throw() {
|
||||
return std::length_error::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::out_of_range, but with stack trace
|
||||
class out_of_range:
|
||||
virtual public mrw::exception, virtual public std::out_of_range {
|
||||
public:
|
||||
out_of_range(const std::string& arg): std::out_of_range(arg) {}
|
||||
virtual const char* what() const throw() {
|
||||
return std::out_of_range::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::runtime_error, but with stack trace
|
||||
class runtime_error:
|
||||
virtual public mrw::exception, virtual public std::runtime_error {
|
||||
public:
|
||||
runtime_error(const std::string& arg): std::runtime_error(arg) {}
|
||||
virtual const char* what() const throw() {
|
||||
return std::runtime_error::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::overflow_error, but with stack trace
|
||||
class overflow_error:
|
||||
virtual public mrw::exception, virtual public std::overflow_error {
|
||||
public:
|
||||
overflow_error(const std::string& arg): std::overflow_error(arg) {}
|
||||
virtual const char* what() const throw() {
|
||||
return std::overflow_error::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::range_error, but with stack trace
|
||||
class range_error:
|
||||
virtual public mrw::exception, virtual public std::range_error {
|
||||
public:
|
||||
range_error(const std::string& arg): std::range_error(arg) {}
|
||||
virtual const char* what() const throw() {
|
||||
return std::range_error::what();
|
||||
}
|
||||
};
|
||||
|
||||
/// Replacement for @c std::underflow_error, but with stack trace
|
||||
class underflow_error:
|
||||
virtual public mrw::exception, virtual public std::underflow_error {
|
||||
public:
|
||||
underflow_error(const std::string& arg): std::underflow_error(arg) {}
|
||||
virtual const char* what() const throw() {
|
||||
return std::underflow_error::what();
|
||||
}
|
||||
};
|
||||
|
||||
//@}
|
||||
}
|
||||
|
||||
|
@@ -12,9 +12,10 @@ EXTRA_DIST = test.dat ${examples_DATA} ${html_DATA} ${pdf_DATA}
|
||||
lib_LTLIBRARIES = libmrw.la libmrwexcstderr.la libmrwexclog4cxx.la
|
||||
|
||||
libmrw_la_SOURCES = mrw.hpp \
|
||||
auto.hpp auto.cpp unistd.hpp \
|
||||
auto.hpp auto.cpp unistd.hpp smartpointer.hpp \
|
||||
stacktrace.hpp stacktrace.cpp exception.hpp exception.cpp \
|
||||
exec.hpp exec.cpp
|
||||
exec.hpp exec.cpp \
|
||||
arg.hpp
|
||||
libmrw_la_LDFLAGS = -version-info @MAJOR@:@MINOR@
|
||||
|
||||
libmrwexcstderr_la_SOURCES = autostacktracestderr.cpp
|
||||
@@ -25,10 +26,14 @@ libmrwexclog4cxx_la_SOURCES = autostacktracelog4cxx.cpp
|
||||
libmrwexclog4cxx_la_LDFLAGS = -version-info @MAJOR@:@MINOR@
|
||||
libmrwexclog4cxx_la_LIBADD = -lmrw
|
||||
|
||||
check_PROGRAMS = auto_test exec_test stacktrace_test mrwexcstderr_test mrwexclog4cxx_test
|
||||
check_PROGRAMS = auto_test smartpointer_test exec_test \
|
||||
stacktrace_test mrwexcstderr_test mrwexclog4cxx_test
|
||||
auto_test_SOURCES = auto_test.cpp
|
||||
auto_test_CPPFLAGS = -I.. -g3
|
||||
auto_test_LDADD = -lmrw -lcppunit
|
||||
smartpointer_test_SOURCES = smartpointer_test.cpp
|
||||
smartpointer_test_CPPFLAGS = -I.. -g3
|
||||
smartpointer_test_LDADD = -lmrw -lcppunit
|
||||
exec_test_SOURCES = exec_test.cpp
|
||||
exec_test_CPPFLAGS = -I.. -g3
|
||||
exec_test_LDADD = -lmrw -lcppunit
|
||||
|
72
mrw/simpletrace.hpp
Normal file
72
mrw/simpletrace.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef __MRW_SIMPLETRACE_HPP__
|
||||
#define __MRW_SIMPLETRACE_HPP__
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
|
||||
// GENERIC TRACER FOR TESTS ---------------------------------------------------
|
||||
// (without file/line)
|
||||
#ifndef __GNUG__
|
||||
#define METHOD(name) mrw::FnTrace fnTrace(this, name)
|
||||
#define FUNCTION(name) mrw::FnTrace fnTrace(0, name)
|
||||
#else
|
||||
#define METHOD mrw::FnTrace fnTrace(this, __FUNCTION__)
|
||||
#define FUNCTION mrw::FnTrace fnTrace(0, __FUNCTION__)
|
||||
#endif
|
||||
#define CALL(name) fnTrace.call(name)
|
||||
#define TRACE(name) fnTrace.trace(name)
|
||||
#define TRACE_OFF mrw::FnTrace::off()
|
||||
#define TRACE_ON mrw::FnTrace::on()
|
||||
#define NO_TRACE mrw::NoTrace noTrace;
|
||||
|
||||
namespace mrw {
|
||||
class FnTrace {
|
||||
public:
|
||||
FnTrace(const void* addr, const std::string& name):
|
||||
_addr(addr), _name(name) {
|
||||
if (_off==0)
|
||||
std::cout<<std::hex<<std::setw(15)<<_addr<<": "<<std::dec
|
||||
<<std::setw(2+_level)<<std::setfill(' ')<<"\\ "
|
||||
<<_name<<std::endl;
|
||||
++_level;
|
||||
}
|
||||
~FnTrace() {
|
||||
--_level;
|
||||
if (_off==0)
|
||||
std::cout<<std::hex<<std::setw(15)<<_addr<<": "<<std::dec
|
||||
<<std::setw(2+_level)<<std::setfill(' ')<<"/ "<<_name
|
||||
<<std::endl;
|
||||
}
|
||||
void call(const std::string& name) {
|
||||
if (_off==0)
|
||||
std::cout<<std::hex<<std::setw(15)<<_addr<<": "<<std::dec
|
||||
<<std::setw(4+_level)<<std::setfill(' ')<<" -> "<<name
|
||||
<<std::endl;
|
||||
}
|
||||
void trace(const std::string& name) {
|
||||
if (_off==0)
|
||||
std::cout<<std::hex<<std::setw(15)<<_addr<<": "<<std::dec
|
||||
<<std::setw(4+_level)<<std::setfill(' ')<<" **** "<<name
|
||||
<<" **** "<<std::endl;
|
||||
}
|
||||
static void off() {
|
||||
++_off;
|
||||
}
|
||||
static void on() {
|
||||
if (_off>0) --_off;
|
||||
}
|
||||
private:
|
||||
const void* _addr;
|
||||
const std::string _name;
|
||||
static unsigned int _level;
|
||||
static unsigned int _off;
|
||||
};
|
||||
unsigned int FnTrace::_level(0);
|
||||
unsigned int FnTrace::_off(0);
|
||||
class NoTrace {
|
||||
public:
|
||||
NoTrace() {TRACE_OFF;}
|
||||
~NoTrace() {TRACE_ON;}
|
||||
};
|
||||
}
|
||||
#endif
|
137
mrw/smartpointer.hpp
Normal file
137
mrw/smartpointer.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#ifndef __MRW__SMARTPOINTER_HPP__
|
||||
#define __MRW__SMARTPOINTER_HPP__
|
||||
|
||||
namespace mrw {
|
||||
|
||||
class PointerCounter {
|
||||
private:
|
||||
unsigned int _cnt;
|
||||
public:
|
||||
PointerCounter():
|
||||
_cnt(1) {
|
||||
}
|
||||
PointerCounter* incr() {
|
||||
++_cnt;
|
||||
return this;
|
||||
}
|
||||
int decr() {
|
||||
return --_cnt;
|
||||
}
|
||||
int get() {
|
||||
return _cnt;
|
||||
}
|
||||
};
|
||||
|
||||
class SmartPointerParent {
|
||||
protected:
|
||||
template<class TYPE>
|
||||
PointerCounter* getCounter(TYPE& sp) {
|
||||
return sp._cnt;
|
||||
}
|
||||
template<class TYPE>
|
||||
typename TYPE::Pointer getPointer(TYPE& sp) {
|
||||
return sp._ptr;
|
||||
}
|
||||
};
|
||||
|
||||
/** @addtogroup AutoTools */
|
||||
//@{
|
||||
|
||||
/** @brief Smart Pointer Implementation
|
||||
@pre #include <mrw/smartpointer.hpp>
|
||||
|
||||
This is a smart pointer that can be casted withing the
|
||||
inheritance of the pointer it is storing.
|
||||
|
||||
A smart pointer is a pointer that is automatically cleaned up
|
||||
when it is no more referenced. Therefore you only allocate
|
||||
memory, but you never free it, just as in Java, cleaning up is
|
||||
done behind the scenes. If you assign a smart pointer to another
|
||||
smart pointer, both point to the same memory. A counter counts
|
||||
the number of references to the object and frees it as soon as
|
||||
the last smart pointer pointing to the same memory has been
|
||||
destroyed.
|
||||
|
||||
A smart pointer is used just as a normal pointer, except that
|
||||
you can assign it only to other smart pointers, not to ordinary
|
||||
pointers, and you don't have to delete the memory allocated. Any
|
||||
memory assigned to a smart pointer is consumed and mustn't be
|
||||
used any more. Never allocate a smart pointer with
|
||||
<code>new</code>, but only the pointer that is stored in the
|
||||
smart pointer!
|
||||
|
||||
@note memory assigned to a smart pointer is consumed
|
||||
*/
|
||||
template<class TYPE> class SmartPointer: public SmartPointerParent {
|
||||
private:
|
||||
typedef TYPE* Pointer;
|
||||
PointerCounter* _cnt;
|
||||
TYPE* _ptr;
|
||||
private:
|
||||
void drop() {
|
||||
if (_cnt && !_cnt->decr()) {
|
||||
delete _cnt; _cnt=0;
|
||||
delete _ptr; _ptr=0;
|
||||
}
|
||||
}
|
||||
private:
|
||||
friend class SmartPointerParent;
|
||||
friend class SmartPointerTest;
|
||||
public:
|
||||
SmartPointer():
|
||||
_cnt(0), _ptr(0) {
|
||||
}
|
||||
SmartPointer(const SmartPointer<TYPE>& o):
|
||||
_cnt(o._cnt?o._cnt->incr():0), _ptr(o._ptr) {
|
||||
}
|
||||
SmartPointer(TYPE* ptr):
|
||||
_cnt(ptr ? new PointerCounter : 0), _ptr(ptr) {
|
||||
}
|
||||
template<class OTHER> SmartPointer(const SmartPointer<OTHER>& o):
|
||||
_cnt(0), _ptr(dynamic_cast<TYPE*>(getPointer(o))) {
|
||||
if (_ptr) _cnt = getCounter(o)->incr();
|
||||
}
|
||||
~SmartPointer() {
|
||||
drop();
|
||||
}
|
||||
SmartPointer& operator=(const SmartPointer<TYPE>& o) {
|
||||
if (o._ptr==_ptr) return *this;
|
||||
drop();
|
||||
_cnt = o._cnt ? o._cnt->incr() : 0;
|
||||
_ptr = o._ptr;
|
||||
return *this;
|
||||
}
|
||||
SmartPointer& operator=(TYPE* ptr) {
|
||||
if (ptr==_ptr) return *this;
|
||||
drop();
|
||||
_cnt = ptr ? new PointerCounter : 0;
|
||||
_ptr = ptr;
|
||||
return *this;
|
||||
}
|
||||
template<class OTHER>
|
||||
SmartPointer& operator=(const SmartPointer<OTHER>& o) {
|
||||
if (getPointer(o)==_ptr) return *this;
|
||||
drop();
|
||||
_ptr = dynamic_cast<TYPE*>(getPointer(o));
|
||||
_cnt = _ptr ? getCounter(o)->incr() : 0;
|
||||
return *this;
|
||||
}
|
||||
TYPE& operator*() {
|
||||
return *_ptr;
|
||||
}
|
||||
const TYPE& operator*() const {
|
||||
return *_ptr;
|
||||
}
|
||||
TYPE* const operator->() {
|
||||
return _ptr;
|
||||
}
|
||||
const TYPE* const operator->() const {
|
||||
return _ptr;
|
||||
}
|
||||
operator bool() {
|
||||
return _ptr!=0;
|
||||
}
|
||||
};
|
||||
//@}
|
||||
}
|
||||
#endif
|
@@ -53,43 +53,6 @@ namespace mrw {
|
||||
- a system with ELF binaries (LINUX, Solaris, ...)
|
||||
- debug information, compile option @c -g
|
||||
- it must be linked with @c -libery and @c -lbfd
|
||||
|
||||
@subsection sttech Technology
|
||||
|
||||
On GNU glibc based systems (Linux), the stack trace is collected
|
||||
with GNU glibc's function @c backtrace(). On other systems
|
||||
(Solaris) it is collected using the GNU gcc's internal function @c
|
||||
__builtin_return_address(). With both functions, at most 50 steps
|
||||
back are collected.
|
||||
|
||||
The evaluation is not done with the glibc library function @c
|
||||
backtrace_symbols(), because this function is unable to print
|
||||
the source file name and line number information. Instead of
|
||||
this, the executable binary is loaded into the memory and
|
||||
evaluated using the bdf library functions. For this the stack
|
||||
tracer needs to know how to find out which executable is
|
||||
running. It is possible to get this information automatically on
|
||||
Linux and Solaris. On other systems, I don't have this
|
||||
information, but you can either tell me, and I integrate support
|
||||
for your system (when I have time to do it), or provide the
|
||||
executable file name as an argument to @c
|
||||
mrw::StackTrace::createSymtable().
|
||||
|
||||
@subsection stdrawbacks Draw Backs
|
||||
|
||||
Unfortunately it is not possible to extract the source file name
|
||||
and line number information if the executable was not compiled
|
||||
with debug option @c -g. But what's worse, it is not possible to
|
||||
ger symbolic information from libraries linked to the
|
||||
executable. Perhaps it could be possible, if I'd add a
|
||||
possibility to read and evaluate these libraries, but that's for
|
||||
a future release.
|
||||
|
||||
@todo Add support to read debugging information from libraries
|
||||
that are linked to the executable.
|
||||
|
||||
@todo Add support for alternative symbol evaluation using @c
|
||||
backtrace_symbols.
|
||||
*/
|
||||
//@{
|
||||
|
||||
@@ -118,6 +81,43 @@ namespace mrw {
|
||||
library.
|
||||
|
||||
@note Symbol evaluation requires the ELF library and an ELF system.
|
||||
|
||||
@section sttech Technology
|
||||
|
||||
On GNU glibc based systems (Linux), the stack trace is collected
|
||||
with GNU glibc's function @c backtrace(). On other systems
|
||||
(Solaris) it is collected using the GNU gcc's internal function @c
|
||||
__builtin_return_address(). With both functions, at most 50 steps
|
||||
back are collected.
|
||||
|
||||
The evaluation is not done with the glibc library function @c
|
||||
backtrace_symbols(), because this function is unable to print
|
||||
the source file name and line number information. Instead of
|
||||
this, the executable binary is loaded into the memory and
|
||||
evaluated using the bdf library functions. For this the stack
|
||||
tracer needs to know how to find out which executable is
|
||||
running. It is possible to get this information automatically on
|
||||
Linux and Solaris. On other systems, I don't have this
|
||||
information, but you can either tell me, and I integrate support
|
||||
for your system (when I have time to do it), or provide the
|
||||
executable file name as an argument to @c
|
||||
mrw::StackTrace::createSymtable().
|
||||
|
||||
@section stdrawbacks Draw Backs
|
||||
|
||||
Unfortunately it is not possible to extract the source file name
|
||||
and line number information if the executable was not compiled
|
||||
with debug option @c -g. But what's worse, it is not possible to
|
||||
ger symbolic information from libraries linked to the
|
||||
executable. Perhaps it could be possible, if I'd add a
|
||||
possibility to read and evaluate these libraries, but that's for
|
||||
a future release.
|
||||
|
||||
@todo Add support to read debugging information from libraries
|
||||
that are linked to the executable.
|
||||
|
||||
@todo Add support for alternative symbol evaluation using @c
|
||||
backtrace_symbols.
|
||||
*/
|
||||
class StackTrace {
|
||||
public:
|
||||
|
Reference in New Issue
Block a user