2.5.1 Design philosophy of the classes
See also section 2.5.5 for more on naming conventions and standard methods.
The general philosophy for C++ classes is that a “half-created” object should not be possible - that is, the constructor should either succeed with a completely usable object or it should throw an exception. This API tries to follow that philosophy, but there are some important exceptions and caveats. (For more on how the C++ and Prolog exceptions interrelate, see section 2.14.)
Most of the PL_*() functions have corresponding wrapper methods. For example, PlTerm::get_atom() calls Plx_get_atom(), which calls PL_get_atom(). If the PL_get_atom() has an error, it creates a Prolog error; the Plx_get_atom() wrapper checks for this and converts the error to a C++ exception, which is thrown; upon return to Prolog, the exception is turned back into a Prolog error. Therfore, code typically does not need to check for errors.
Some functions return false
to indicate either failure
or an error, for example PlTerm::unify_term();
for such methods, a check is made for an error and an exception is
thrown, so the return value of
false
only means failure. (The whole thing can be wrapped
in
PlCheckFail(), in which case a PlFail
exception is thrown, which is converted to failure in Prolog.) For more
on this, see
section 2.5.4, and for handling
failure, see
section 2.12.1.
For PL_*() functions that take or return char*
or
wchar_t*
values, there are also wrapper functions and
methods that use std::string
or std::wstring
.
Because these copy the values, there is usually no need to enclose the
calls with
PlStringBuffers
(which wraps PL_STRING_MARK() and
PL_STRING_RELEASE()). See also the rationale for string:
section 2.7.2.
Many of the classes (PlAtom
, PlTerm
, etc.)
are thin wrappers around the C interface's types (atom_t
,
term_t
, etc.). As such, they inherit the concept of “null” from
these types (which is abstracted as PlAtom::null
,
PlTerm::null
, etc., which typically is equivalent to
0
). Normally, you shouldn't need to check whether the
object is “fully created” , for the rare situations where a
check is needed, the methods is_null()
and not_null() are provided.
Most of the classes have constructors that create a “complete” object. For example,
PlAtom foo("foo");
will ensure that the object foo
is useable and will
throw an exception if the atom can't be created. However, if you choose
to create a PlAtom
object from an atom_t
value, no checking is done (similarly, no checking is done if you create
a PlTerm
object from a term_t
value).
In many situations, you will be using a term; for these, there are special constructors. For example:
PlTerm_atom foo("foo"); // Same as PlTerm(PlAtom("foo")) PlTerm_string str("a string");
To help avoid programming errors, some of the classes do not have a
default “empty” constructor. For example, if you with to
create a
PlAtom
that is uninitialized, you must explicitly use
PlAtom(PlAtom::null)
.
This make some code a bit more cumbersome because you can't omit the
default constructors in struct initalizers.
Many of the classes have an as_string()
method8This might be changed in
future to to_string(), to be consistent with std::to_string()
,
which is useful for debugging.
The method names such as
as_int32_t() were chosen itnstead of to_int32_t() because
they imply that the representation is already an int32_t
,
and not that the value is converted to a int32_t
. That is,
if the value is a float, int32_t
will fail with an error
rather than (for example) truncating the floating point value to fit
into a 32-bit integer.
Many of the classes wrap long-lived items, such as atoms, functors,
predicates, or modules. For these, it's often a good idea to define them
as static
variables that get created at load time, so that
a lookup for each use isn't needed (atoms are unique, so
PlAtom("foo")
requires
a lookup for an atom foo
and creates one if it isn't
found).
C code sometimes creates objects “lazily” on first use:
void my_function(...) { static atom_t ATOM_foo = 0; ... if ( ! foo ) foo = PL_new_atom("foo"); ... }
For C++, this can be done in a simpler way, because C++ will call a
local “static
” constructor on first use.
void my_function(...) { static PlAtom ATOM_foo("foo"); }
The class PlTerm
(which wraps term_t
) is
the most used. Although a PlTerm
object can be created from
a term_t
value, it is intended to be used with a
constructor that gives it an initial value. The default constructor
calls PL_new_term_ref() and throws an exception if this fails.
The various constructors are described in
section 2.5.6. Note that the
default constructor is not public; to create a “variable” term,
you should use the subclass constructor PlTerm_var().