Haiku BObjectList: Type Safety, Ownership, and Mutation
Use Haiku BObjectList with explicit ownership, checked insertion, safe removal, stable identity, and predictable copying under allocation failure.
BObjectList<T, Owning> is a Support Kit wrapper around BList that adds type-safe access, search, insertion, sorting, and optional object ownership. Its boolean template argument is a lifetime policy, not a performance hint. With the default Owning = false, the container stores pointers but does not delete the pointed-to objects when it is destroyed. With Owning = true, destroying or emptying the list deletes the objects it owns.
That difference affects every insertion, removal, replacement, copy, and shutdown path. A list that owns an object must not share that same object with another owning list. A non-owning list must not outlive the objects it references. A copy of an owning list is documented to clone its items, while a copy of a non-owning list copies pointers. Those semantics can prevent double-deletes when understood, but they also make “just copy the list” a nontrivial operation.
Choose one owner for every object
Decide ownership before declaring the list. An owning list is useful when the collection is the authoritative owner of heap objects and its elements should be deleted when removed or when the list is destroyed. A non-owning list is useful for indexes, temporary views, or lists of objects whose lifetime belongs elsewhere.
Make the decision visible in the type:
BObjectList<Record, true> ownedRecords;
BObjectList<Record, false> visibleRecords;
The first list owns its Record pointers; the second does not. If both point at the same records, only one should own them. If visibleRecords is a view of ownedRecords, ensure it is cleared before records are removed or deleted. A non-owning container cannot protect you from a dangling pointer.
Avoid inserting stack objects into an owning list: destruction would attempt to delete a non-heap address. Likewise, do not insert the same pointer twice into one owning list unless its duplicate-item semantics and deletion behavior are explicitly intended; repeated deletion of one allocation is invalid. Prefer stable IDs or separate non-owning references when the same object appears in multiple UI groupings.
Check every mutation result
AddItem() and indexed insertion return bool; allocation failure or invalid index can leave the list unchanged. Do not update a parallel count or UI model as if insertion succeeded until the result is checked. AddList() similarly can fail when growing storage. For a transactional model update, prepare the object, insert it, and only then publish the associated state.
RemoveItem() supports a deleteIfOwning argument, defaulting to true. A successful removal from an owning list therefore normally deletes the object. If the caller needs to retain the object, use the documented removal path that returns the pointer or explicitly disable deletion where the API allows it. RemoveItemAt() returns the removed pointer, allowing ownership to transfer to the caller. The caller must then either delete it or transfer it to another clearly defined owner.
ReplaceItem() deletes the old item when the list owns it; SwapWithItem() instead returns the replaced pointer without deleting it. These are not interchangeable. Replacing with an alias of the old pointer can result in a dangling list entry if the old value is deleted, so do not pass the same object back into an owning replacement operation.
MakeEmpty() takes a deleteIfOwning parameter and returns no item pointers. The default deletes owned items. Passing false clears the list without deleting its owned objects, so the caller must already have another way to retain every pointer that still needs cleanup; after the call, the list cannot be used to enumerate those objects. Capture or transfer the references before clearing, and ensure each object is accounted for exactly once.
Copying has different semantics by ownership mode
The current header documents that copying an owning list makes copies of all items. Its implementation uses the item copy constructor to build separate objects. That requires T to be copyable in the expected way, can allocate substantially, and can fail to produce a fully useful clone if item copies fail. Do not use copying as a cheap way to snapshot a large owning model.
Copying a non-owning list copies pointer values, not the pointed-to objects. The original owner must outlive every copied list. Assignment has corresponding clone semantics; it is not a move operation and should not be treated as an ownership transfer. If a snapshot needs independent state, define an explicit clone routine with error reporting rather than relying on shallow pointer copying.
These rules are especially important in UI models. A view can hold an owning list of rows, while a filter or selection list contains non-owning pointers to those rows. Removing a row must update both lists before the object is destroyed. Alternatively, use stable IDs and look up current rows in the owner. Stable identities are safer when sorted positions or filtering can change.
Ordering, search, and index invalidation
BObjectList offers sort and search operations, including predicate and binary-insertion helpers. A binary search is only correct when the list is sorted with the same comparison rule. If an object’s sort key changes in place, the list’s ordering is invalid even though no pointer was added or removed. Re-sort or remove/update/reinsert the item before relying on binary search.
Store a deterministic comparator. Avoid comparators that depend on current locale settings, mutable global state, or object fields changing concurrently while sorting. Sorting changes positions; callers should not preserve an index as object identity. Use a pointer with managed lifetime or a stable key.
Iteration and mutation need a synchronization rule. BObjectList does not make the pointed-to objects thread-safe and should not be assumed internally synchronized for concurrent mutation. Protect list structure with an appropriate lock, keep iteration within the lock, and avoid calling arbitrary callbacks while holding a lock if they may reenter the model. On a window-owned model, marshal mutations through the owning looper.
Avoid accidental ownership transfer in UI code
List controls such as BListView also manage item objects under their own lifecycle rules. Do not place a list-item pointer into an owning BObjectList while the control also owns and deletes it unless the ownership contract explicitly coordinates that relationship. A safer design is to keep domain objects in one model owner and let UI list items contain stable IDs or non-owning links.
When a UI selection changes, do not delete a domain object merely because the row was removed from a temporary selection list. Selection is usually a non-owning view of a larger model. Separate “remove from this projection,” “remove from authoritative model,” and “destroy the object” into distinct operations.
Failure and shutdown design
If object construction succeeds but AddItem() fails, delete the newly created object if ownership never transferred. If insertion succeeds into an owning list, do not manually delete the pointer while it remains there. On shutdown, destroy non-owning views before the owner when those views may still access objects; clear callbacks and pending messages first.
Use RAII for objects before transfer into an owning list. Once insertion succeeds, release the local owner; if it fails, the local smart pointer cleans up. For removal, return ownership into an RAII wrapper immediately. This gives allocation failures and early returns a consistent cleanup path.
Test ownership behavior explicitly
Test owning and non-owning variants separately. Count object constructions/destructions across list destruction, RemoveItem, RemoveItemAt, ReplaceItem, SwapWithItem, and MakeEmpty(false). Test failed insertion and invalid indexes. Copy an owning list and verify distinct object addresses and independent mutations; copy a non-owning list and confirm the external owner controls the lifetime.
Also test sort-key mutation, binary search only after sorting, duplicate pointers, and selection/model removal. Use sanitizers where the Haiku build toolchain supports them, or a small destructor counter on a native test application. A green UI screenshot cannot prove the model has no double-free or dangling-reference path.
BObjectList is valuable when its type safety and ownership policy match the model. The code is safer when one component owns each allocation, mutation results are checked, copies are explicit about deep versus shallow behavior, and indices are never mistaken for object identity. Those rules apply equally to model data, list-item projections, and short-lived search/filter results.
Related:
- Haiku BListView: Item Lifetime, Selection, and Stable Model Updates
- Haiku BOutlineListView: Tree Structure, Visible Rows, and Safe Removal
Sources: