PlTerm is a struct that contains a term_t (as if it were declared struct { term_t C_; } in C), with additional “methods”. So, auto t_low = A1 is the essentially the same as PlTerm t_low=A1, which simply copies the term_t value. In C++, when t_low goes out of scope, it calls the destructor – currently the destructor does nothing but @jan has proposed that it call PL_free_term_ref(). To do this correctly would require reference counting, I think.
A variant of assignment is PlTerm t_low(A1) – this also creates a new object that wraps a term_t, but uses the “copy constructor” instead of assignment; the net result is the same as auto t_low=A1. A copy constructor is also invoked when an object is passed as an argument to a function, or as a return value. (In theory, a copy constructor could be different from the assignment operator, but in practice they’re usually the same.) There is also a “move assignment” and “move constructor” which allow defining an optimized version of copying for certain situations – SWI-cpp2.h doesn’t define any move operations, so they default to the copy operations.
C++ allows overriding the copy operations that are done by assignment and function calls. It’s also possible to define a class that doesn’t allow copying – that’s what I was thinking of with the PlTerm_new constructor that would have a destructor that calls PL_free_term_ref().
C++ has two “smart pointers” that wrap an ordinary C pointer and handle automatic deletion: shared_ptr and unique_ptr (they both override assignment and the “copy constructor”). Shared_ptr has a reference count, so the underlying pointer is deleted only when its reference count goes to zero. Unique_ptr ensures that a pointer has only a single “owner”, by transferring ownership on copy and zeroing the source – it doesn’t allow assignment. For example:
std::unique_ptr<MyStruct> p(new MyStruct);
std::unique_ptr<MyStruct> q;
// Not allowed: q = p;
q.reset(p.release());
is roughly equivalent to:
MyStruct *p = malloc(size of MyStruct);
MyStruct *q;
q = p; p = 0;
...
free(q);
free(p); // Does nothing because q==0
My proposal of PlTerm_new is that it would have semantics similar to unique_ptr. (Unique_ptr has a few other operations: get(), which is dangerous and accesses the underlying pointer; release(), which explicitly transfers ownership; reset(), which sets the underlying pointer.) It’s relatively straightforward to implement these correctly.
Alternatively, we could make PlTerm into a reference-counted term_t. It’s trickier to get everything correct, but it can be done. (The source code for shared_ptr is fairly unreadable, because it’s quite general; but there exist some simplified implementations, such as GitHub - SRombauts/shared_ptr: A minimal shared/unique_ptr implementation to handle cases where boost/std::shared/unique_ptr are not available.)
As an alternative, it might be possible to simply document how to use unique_ptr and shared_ptr to do what we want (both of these can define a custom “deleter”, for example). I’d have to think a bit about that possibility.