manpagez: man pages & more
info ginac
Home | html | info | man
[ < ] [ > ]   [ << ] [ 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:

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_nodes 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] [ ? ]
© manpagez.com 2000-2024
Individual documents may contain additional copyright information.