I'm devising an API - or actually, a wrapper API for another, lower-level API - in a programming language with objects.
This API represents some entity E which is reference-counted (for example - a hardware resource); but - the reference counting is by the underlying API, or by the OS kernel etc.; so there is an "increase ref count" and "decrease ref count" lower-level functions for these entities. Let's assume we consider the refcount increases/decreases as "expensive" actions.
So, I'm designing a class for these entities. An object of class E "holds" one unit of ref count, i.e. we increase the ref count on its construction and decrease it on its destruction.
I am now mulling over the reference ownership, copyability/assignability of this class. Let's assume it's copyable if-and-only-if it's assignable and discusss those two features as just "copyability". So, should such E objects be copyable, and how?
... with options I can think about:
- Make class
Enaively copyable, supportinge_1 = e_2statements; the copy code will increase the refcount of the underlying entity (for entity_1) by 1, i.e. do the expensive thing. - Make class
Emovable but not (naively) copyable, i.e.e_2 = e_1would typically fail to compile; to actually copy anEand increase the reference count, we would have aE::clone()method. - Make class
Esupport both owning-reference and non-owning-reference instances, i.e. possibly not increase and decrease the refcount, in which case an instance depends on another, owning, reference to live while being used. In this case, the naive assignment would create a non-owning referencee2(even ife_1is an owning reference), without touching the refcount. - Separate the notion of ownership from the class, so that
Eis always a non-owning reference, but we have anowner<T>class template, or generic class, which adds the ownership semantics and from which one can get non-owningEinstances.
What are the pros and cons you find for each of these options?
Notes:
- I used C++ for my pseudocode, and I am writing this in C++, but if you can answer this more generally, please do.
- If you need more contextual information to provide an answer, please ask for it, or answer "if X then A, otherwise B".
Emodel? Does it model a reference to an entity, meaning that if I create a copy, then it is natural that changes made to one are reflected in the other. Or does it model the entity itself, and changes made to a copy should not be made to the original. That is very fundamental in deciding to what extent creating copies should/can be supported and how.Emovable but not (naively) copyable" is the default behaviour in Rust. In fact Rust doesn't even have "non-naive" copies, and even those are disabled by default. And I strongly suggest you do that..clone()then? Rust allows implicit copies of things that explicitly implementCopytrait (a.k.a. interface). This is just a marker, you don't actually implementCopy, copies are always naive in Rust (byte by byte). This includes primitive types like integers, bools, floats, etc. But vast majority of types are notCopy. Especially types that allocate stuff, like strings or vectors.x = ystatement. It's just bad. And explicit.clone()calls are fine. Makes you think twice before doing that. In Rust you actually haveArctype (which stands for atomic reference count) which matches well your case: it keeps a ref count, which is bumped on.clone()call, and decreased whenArcinstance goes out of scope (Rust also has RAII like C++).