2.5.6.5 Discussion of the sample PlBlob code
- PL_BLOB_DEFINITION(MyBlob, "my_blob") creates a
PL_blob_tstructure with the wrapper functions and flags set toPL_BLOB_NOCOPY. It should be declared outside thePlBlobclass and should not be markedconst- otherwise, a runtime error can occur.13The 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
MyBlobstruct is a subclass ofPlBlob. See below for a discussion of the default behaviors.MyBlobcontains a pointer to aMyConnectionobject and keeps a copy of the connection's name. TheMyConnectionobject is handled by astd::unique_ptrsmart pointer, so that it is automatically freed when theMyBlobobject is freed.- A default constructor is defined - this is needed for the load() and
save() methods; it invokes the
PlBlobconstructor. - The
MyBlobclass must not provide a copy or move constructor, nor an assignment operator (PlBlob has these asdelete, 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_blobdefinition. 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>::awcuire() and fills in thesymbolfield.MyBlobmust not override this - it is not a virtual method.- 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.
- The MyBlob(connection_name) constructor creates a
MyConnectionobject. 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 forMyConnectionalso did an open and threw an exception on failure.) - The
PL_BLOB_SIZEis 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.14It isn't enough to
just catch exceptions; for example, if the code does
throw PlUnknownErro("..."), 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
MyConnectionobject 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_NEWLINEbit 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
indexqsave_program/[1,2]qsave_program/[1,2]).15If these weren't specified, the defaults would save the internal form of the blob, which is probably not what you want.
- 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 theMyBlobdestructor will be called).If PlTerm::unify_blob() is called with a pointer to a
std::unique_ptr, it takes ownership of the object by calling std::unique_ptr<PlBlob>::release(). This sets ref tonullptr, so any attempt to use ref after a successful call to PlTerm::unify_blob() will be an error.If you wish to create a
MyBlobobject instead of aPlBlobobject, 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
MyBlobpointer using the PlBlobV<MyBlob>::cast_ex() function, which will throw atype_errorif 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