[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
6.5.2 A minimalistic example
Now we will start implementing a new class mystring
that allows
placing character strings in algebraic expressions (this is not very useful,
but it's just an example). This class will be a direct subclass of
basic
. You can use this sample implementation as a starting point
for your own classes (3).
The code snippets given here assume that you have included some header files as follows:
#include <iostream> #include <string> #include <stdexcept> using namespace std; #include <ginac/ginac.h> using namespace GiNaC; |
Now we can write down the class declaration. The class stores a C++
string
and the user shall be able to construct a mystring
object from a C or C++ string:
class mystring : public basic { GINAC_DECLARE_REGISTERED_CLASS(mystring, basic) public: mystring(const string & s); mystring(const char * s); private: string str; }; GINAC_IMPLEMENT_REGISTERED_CLASS(mystring, basic) |
The GINAC_DECLARE_REGISTERED_CLASS
and GINAC_IMPLEMENT_REGISTERED_CLASS
macros are defined in ‘registrar.h’. They take the name of the class
and its direct superclass as arguments and insert all required declarations
for the RTTI system. The GINAC_DECLARE_REGISTERED_CLASS
should be
the first line after the opening brace of the class definition. The
GINAC_IMPLEMENT_REGISTERED_CLASS
may appear anywhere else in the
source (at global scope, of course, not inside a function).
GINAC_DECLARE_REGISTERED_CLASS
contains, among other things the
declarations of the default constructor and a couple of other functions that
are required. It also defines a type inherited
which refers to the
superclass so you don't have to modify your code every time you shuffle around
the class hierarchy. GINAC_IMPLEMENT_REGISTERED_CLASS
registers the
class with the GiNaC RTTI (there is also a
GINAC_IMPLEMENT_REGISTERED_CLASS_OPT
which allows specifying additional
options for the class, and which we will be using instead in a few minutes).
Now there are seven member functions we have to implement to get a working class:
-
mystring()
, the default constructor. -
void archive(archive_node & n)
, the archiving function. This stores all information needed to reconstruct an object of this class inside anarchive_node
. -
mystring(const archive_node & n, lst & sym_lst)
, the unarchiving constructor. This constructs an instance of the class from the information found in anarchive_node
. -
ex unarchive(const archive_node & n, lst & sym_lst)
, the static unarchiving function. It constructs a new instance by calling the unarchiving constructor. -
int compare_same_type(const basic & other)
, which is used internally by GiNaC to establish a canonical sort order for terms. It returns 0, +1 or -1, depending on the relative order of this object and theother
object. If it returns 0, the objects are considered equal. Please notice: This has nothing to do with the (numeric) ordering relationship expressed by<
,>=
etc (which cannot be defined for non-numeric classes). For example,numeric(1).compare_same_type(numeric(2))
may return +1 even though 1 is clearly smaller than 2. Every GiNaC class must provide acompare_same_type()
function, even those representing objects for which no reasonable algebraic ordering relationship can be defined. -
And, of course,
mystring(const string & s)
andmystring(const char * s)
which are the two constructors we declared.
Let's proceed step-by-step. The default constructor looks like this:
mystring::mystring() : inherited(&mystring::tinfo_static) {} |
The golden rule is that in all constructors you have to set the
tinfo_key
member to the &your_class_name::tinfo_static
(4). Otherwise
it will be set by the constructor of the superclass and all hell will break
loose in the RTTI. For your convenience, the basic
class provides
a constructor that takes a tinfo_key
value, which we are using here
(remember that in our case inherited == basic
). If the superclass
didn't have such a constructor, we would have to set the tinfo_key
to the right value manually.
In the default constructor you should set all other member variables to
reasonable default values (we don't need that here since our str
member gets set to an empty string automatically).
Next are the three functions for archiving. You have to implement them even if you don't plan to use archives, but the minimum required implementation is really simple. First, the archiving function:
void mystring::archive(archive_node & n) const { inherited::archive(n); n.add_string("string", str); } |
The only thing that is really required is calling the archive()
function of the superclass. Optionally, you can store all information you
deem necessary for representing the object into the passed
archive_node
. We are just storing our string here. For more
information on how the archiving works, consult the ‘archive.h’ header
file.
The unarchiving constructor is basically the inverse of the archiving function:
mystring::mystring(const archive_node & n, lst & sym_lst) : inherited(n, sym_lst) { n.find_string("string", str); } |
If you don't need archiving, just leave this function empty (but you must
invoke the unarchiving constructor of the superclass). Note that we don't
have to set the tinfo_key
here because it is done automatically
by the unarchiving constructor of the basic
class.
Finally, the unarchiving function:
ex mystring::unarchive(const archive_node & n, lst & sym_lst) { return (new mystring(n, sym_lst))->setflag(status_flags::dynallocated); } |
You don't have to understand how exactly this works. Just copy these
four lines into your code literally (replacing the class name, of
course). It calls the unarchiving constructor of the class and unless
you are doing something very special (like matching archive_node
s
to global objects) you don't need a different implementation. For those
who are interested: setting the dynallocated
flag puts the object
under the control of GiNaC's garbage collection. It will get deleted
automatically once it is no longer referenced.
Our compare_same_type()
function uses a provided function to compare
the string members:
int mystring::compare_same_type(const basic & other) const { const mystring &o = static_cast<const mystring &>(other); int cmpval = str.compare(o.str); if (cmpval == 0) return 0; else if (cmpval < 0) return -1; else return 1; } |
Although this function takes a basic &
, it will always be a reference
to an object of exactly the same class (objects of different classes are not
comparable), so the cast is safe. If this function returns 0, the two objects
are considered equal (in the sense that A-B=0), so you should compare
all relevant member variables.
Now the only thing missing is our two new constructors:
mystring::mystring(const string & s) : inherited(&mystring::tinfo_static), str(s) {} mystring::mystring(const char * s) : inherited(&mystring::tinfo_static), str(s) {} |
No surprises here. We set the str
member from the argument and
remember to pass the right tinfo_key
to the basic
constructor.
That's it! We now have a minimal working GiNaC class that can store strings in algebraic expressions. Let's confirm that the RTTI works:
ex e = mystring("Hello, world!"); cout << is_a<mystring>(e) << endl; // -> 1 (true) cout << ex_to<basic>(e).class_name() << endl; // -> mystring |
Obviously it does. Let's see what the expression e
looks like:
cout << e << endl; // -> [mystring object] |
Hm, not exactly what we expect, but of course the mystring
class
doesn't yet know how to print itself. This can be done either by implementing
the print()
member function, or, preferably, by specifying a
print_func<>()
class option. Let's say that we want to print the string
surrounded by double quotes:
class mystring : public basic { ... protected: void do_print(const print_context & c, unsigned level = 0) const; ... }; void mystring::do_print(const print_context & c, unsigned level) const { // print_context::s is a reference to an ostream c.s << '\"' << str << '\"'; } |
The level
argument is only required for container classes to
correctly parenthesize the output.
Now we need to tell GiNaC that mystring
objects should use the
do_print()
member function for printing themselves. For this, we
replace the line
GINAC_IMPLEMENT_REGISTERED_CLASS(mystring, basic) |
with
GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(mystring, basic, print_func<print_context>(&mystring::do_print)) |
Let's try again to print the expression:
cout << e << endl; // -> "Hello, world!" |
Much better. If we wanted to have mystring
objects displayed in a
different way depending on the output format (default, LaTeX, etc.), we
would have supplied multiple print_func<>()
options with different
template parameters (print_dflt
, print_latex
, etc.),
separated by dots. This is similar to the way options are specified for
symbolic functions. See section GiNaC's expression output system, for a more in-depth description of the
way expression output is implemented in GiNaC.
The mystring
class can be used in arbitrary expressions:
e += mystring("GiNaC rulez"); cout << e << endl; // -> "GiNaC rulez"+"Hello, world!" |
(GiNaC's automatic term reordering is in effect here), or even
e = pow(mystring("One string"), 2*sin(Pi-mystring("Another string"))); cout << e << endl; // -> "One string"^(2*sin(-"Another string"+Pi)) |
Whether this makes sense is debatable but remember that this is only an example. At least it allows you to implement your own symbolic algorithms for your objects.
Note that GiNaC's algebraic rules remain unchanged:
e = mystring("Wow") * mystring("Wow"); cout << e << endl; // -> "Wow"^2 e = pow(mystring("First")-mystring("Second"), 2); cout << e.expand() << endl; // -> -2*"First"*"Second"+"First"^2+"Second"^2 |
There's no way to, for example, make GiNaC's add
class perform string
concatenation. You would have to implement this yourself.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |