2.5.7.6 Discussion of the sample PlBlob code
- PL_BLOB_DEFINITION(MyBlob, "my_blob") creates a
PL_blob_t
structure with the wrapper functions and flags set toPL_BLOB_NOCOPY
. It should be declared outside thePlBlob
class and should not be markedconst
- otherwise, a runtime error can occur.18The cause of the runtime error is not clear, but possibly has to do with the order of initializing globals, which is unspecified for C++. - The
MyBlob
struct is a subclass ofPlBlob
. See below for a discussion of the default behaviors.MyBlob
contains a pointer to aMyConnection
object and keeps a copy of the connection's name. TheMyConnection
object is handled by astd::unique_ptr
smart pointer, so that it is automatically freed when theMyBlob
object is freed.- A default constructor is defined - this is needed for the
load() and save() methods; it invokes the
PlBlob
constructor. - The
MyBlob
class must not provide a copy or move constructor, nor an assignment operator (PlBlob has these as delete, so if you try to use one of these, you will get a compile-time error). PlBlob
’s constructor setsblob_t_
to a pointer to themy_blob
definition. This is used for run-time consistency checking by the various callback functions and for constructing error terms (see PlBlob::symbol_term()).PlBlob
’s acquire() is called by PlBlobV<MyBlob>::acquire() and fills in thesymbol_
field.MyBlob
must not override this - it is not a virtual method. Thesymbol_
field can be accessed by PlBlob::symbol_term().- PlBlob::symbol_term() Creates a term from the blob, for use
in error terms. It is always safe to use this; if the symbol hasn't been
set (because acquire() hasn't been called),
symbol_term() returns a “var” term - this can be
checked with PlTerm::is_variable().
- The MyBlob(connection_name) constructor creates a
MyConnection
object. If this fails, an exception is thrown. The constructor then calls MyConnection::open() and throws an exception if that fails. (The code would be similar if instead the constructor forMyConnection
also did an open and threw an exception on failure.) - The
PL_BLOB_SIZE
is boilerplate that defines a blob_size_() method that is used when the blob is created. - The destructor MyBlob() is called when the blob is
released by the garbage collector and in turn calls the MyBlob::close(),
throwing away the result. If there is an error, a message is printed
because there is no other way report the error. For this reason, it is
preferred that the program explicitly calls the
close_my_blob/1
predicate, which can raise an error. One way of doing this is by using
the at_halt/1
hook.
- The MyBlob::close() method is called by either the destructor
or by the close_my_blob/1
predicate. Because it can be called by the garbage collector, which does
not provide the usual environment and which may also be in a different
thread, the only Prolog function that can be called is
PlAtom::unregister_ref(); and the MyBlob::close() method
must not throw an exception.19It
isn't enough to just catch exceptions; for example, if the code throws
PlUnknownError("...")
, that will try to create a Prolog term, which will crash because the environment for creating terms is not available. Because there is no mechanism for reporting an error, the destructor prints a message on failure (calling PL_warning() would cause a crash).PlBlob::close() calls MyConnection::close() and then frees the object. Error handling is left to the caller because of the possibility that this is called in the context of garbage collection. It is not necessary to free the
MyConnection
object here - if it is not freed, thestd::unique_ptr<MyConnection>
’s destructor would free it. - PlBlob::MyBlobError() is a convenience method for creating
errror terms.
- PlBlob::compare_fields()
makes the blob comparison function more deterministic by comparing the
name fields; if the names are the same, the comparison will be done by
comparing the addresses of the blobs (which is the default behavior for
blobs defined using the C API).
PlBlob::compare_fields()
is called by PlBlobV<PlBlob>::compare(), which
provides the default comparison if PlBlob::compare_fields()
returns
0
(``equal” ).The _b_data argument is of type
const PlBlob*
- this is cast toconst MyBlob*
using astatic_cast
. This is safe because Prolog guarantees that PlBlobV<PlBlob>::compare() will only be called if both blobs are of the same type. - PlBlob::write_fields()
outputs the name and the status of the connection, in addition to the
default of outputting the blob type and its address. This is for
illustrative purposes only; an alternative is to have a my_blob_properties/2
predicate to provide the information.
The flags argument is the same as given to PlBlobV<PlBlob>::write(), which is a bitwise or of zero or more of the
PL_WRT_*
flags that were passed in to the caling PL_write_term() (defined inSWI-Prolog.h
). The flags do not have thePL_WRT_NEWLINE
bit set, so it is safe to call PlTerm::write() and there is no need for writing a trailing newline.If anything in PlBlob::write_fields() throws a C++ exception, it will be caught by the calling PlBlobV<PlBlob>::write() and handled appropriately.
- PlBlob::save() and PlBlob::load() are not defined, so
the defaults are used - they throw an error on an attempt to save the
blob (e.g., by using qsave_program/[1,2]).20The
C API defaults would save the internal form of the blob, which is
probably not what you want, so the C++ API throws an error as its
default.
- create_my_blob/2
predicate:
std::unique_ptr<PlBlob>()
creates a MyBlob that is deleted when it goes out of scope. If an exception occurs between the creation of the blob or if the call to unify_blob() fails, the pointer will be automatically freed (and theMyBlob
destructor will be called).PlTerm::unify_blob() is called with a pointer to a
std::unique_ptr
, which takes ownership of the object by calling std::unique_ptr<PlBlob>::release() and passes the pointer to Prolog, which then owns it. This also sets ref tonullptr
, so any attempt to use ref after a call to PlTerm::unify_blob() will be an error.If you wish to create a
MyBlob
object instead of aPlBlob
object, a slightly different form is used:auto ref = std::make_unique<MyBlob>(...); ... std::unique_ptr<PlBlob> refb(ref.release()); PlCheckFail(A2.unify_blob(&refb)); return true;
- close_my_blob/1
predicate:
- The argument is turned into a
MyBlob
pointer using the PlBlobV<MyBlob>::cast_ex() function, which will throw atype_error
if the argument isn't a blob of the expected type. - The MyBlob::close() method is called - if it fails, a Prolog
error is thrown.
- The argument is turned into a