Implementation:NVIDIA DALI C API V2 Ref Counting
| Knowledge Sources | |
|---|---|
| Domains | Data_Pipeline, C_API |
| Last Updated | 2026-02-08 16:00 GMT |
Overview
The reference counting header provides an intrusive reference counting base class and a smart pointer template for managing the lifetime of DALI C API v2 objects.
Description
This header file defines the core memory management primitives used by all DALI C API v2 data objects. It belongs to the dali::c_api namespace and provides two primary types: RefCountedObject (the base class) and RefCountedPtr<T> (the smart pointer).
RefCountedObject is a base class that provides thread-safe intrusive reference counting through an std::atomic<int> member initialized to 1. The IncRef() method uses memory_order_relaxed for the atomic increment (sufficient since incrementing only requires visibility, not ordering). The DecRef() method uses memory_order_acq_rel to ensure that all prior modifications are visible before the potential deletion, and automatically deletes the object when the count reaches zero via delete this. The virtual destructor enables correct polymorphic cleanup.
RefCountedPtr<T> is a smart pointer that manages RefCountedObject-derived objects. It supports: construction from a raw pointer (optionally incrementing the ref count for shared ownership), copy construction (increments ref), move construction (transfers ownership without ref changes), copy assignment (with self-assignment safety), move assignment, reset() (decrements and nullifies), and release() (transfers ownership to the caller without decrementing). It also provides converting constructors and assignments for derived-to-base conversions when U* is convertible to T*.
The RefCountedPtr is designed for use in the C API where objects cross the C/C++ boundary: when a handle is passed out to C code, release() transfers ownership; when C code calls DecRef to destroy the object, the ref count drops to zero and the destructor runs.
Usage
Use RefCountedObject as a base class for any C API v2 object that needs reference-counted lifetime management (e.g., ITensor, ITensorList). Use RefCountedPtr<T> within C++ code to safely manage these objects. When passing objects out through the C API, use release() to hand off ownership; when receiving them back, construct a RefCountedPtr with inc_ref=true to share ownership.
Code Reference
Source Location
- Repository: NVIDIA_DALI
- File: dali/c_api_2/ref_counting.h
- Lines: 1-152
Signature
namespace dali::c_api {
class RefCountedObject {
public:
int IncRef() noexcept; // Returns new ref count
int DecRef() noexcept; // Returns new ref count; deletes this when 0
int RefCount() const noexcept;
virtual ~RefCountedObject() = default;
private:
std::atomic<int> ref_{1};
};
template <typename T>
class RefCountedPtr {
public:
constexpr RefCountedPtr() noexcept = default;
explicit RefCountedPtr(T *ptr, bool inc_ref = false) noexcept;
~RefCountedPtr();
// Copy/move constructors and assignments (with derived-to-base conversion)
RefCountedPtr(const RefCountedPtr<T> &other) noexcept;
template <typename U> RefCountedPtr(const RefCountedPtr<U> &other) noexcept;
template <typename U> RefCountedPtr(RefCountedPtr<U> &&other) noexcept;
void reset() noexcept;
[[nodiscard]] T *release() noexcept;
constexpr T *operator->() const & noexcept;
constexpr T &operator*() const & noexcept;
constexpr T *get() const & noexcept;
explicit constexpr operator bool() const noexcept;
private:
T *ptr_ = nullptr;
};
} // namespace dali::c_api
Import
#include "dali/c_api_2/ref_counting.h"
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| ptr | T * | Yes (constructor) | Pointer to a RefCountedObject-derived object |
| inc_ref | bool | No | If true, increments ref count on construction (default: false, takes ownership) |
Outputs
| Name | Type | Description |
|---|---|---|
| int (from IncRef) | int | New reference count after increment |
| int (from DecRef) | int | New reference count after decrement (object deleted if 0) |
| int (from RefCount) | int | Current reference count |
| T * (from release) | pointer | Raw pointer with ownership transferred to caller |
Usage Examples
Basic Reference Counting
#include "dali/c_api_2/ref_counting.h"
using namespace dali::c_api;
class MyObject : public RefCountedObject {
public:
int value = 42;
};
// Create with initial ref count of 1
RefCountedPtr<MyObject> ptr(new MyObject());
assert(ptr->RefCount() == 1);
// Copy increases ref count
RefCountedPtr<MyObject> ptr2 = ptr;
assert(ptr->RefCount() == 2);
// Reset decreases ref count
ptr.reset();
assert(ptr2->RefCount() == 1);
// When ptr2 goes out of scope, ref count drops to 0 and object is deleted
Passing Objects Through the C API Boundary
// C++ side: create and hand off to C
RefCountedPtr<ITensor> tensor = ITensor::Create(placement);
daliTensor_h handle = tensor.release(); // ref count stays at 1, ownership to C
// C side: use the handle
daliTensorResize(handle, 3, shape, DALI_FLOAT32, "HWC");
// C side: done with the handle
int ref;
daliTensorDecRef(handle, &ref); // ref == 0, object is destroyed
// C++ side: share ownership with C
RefCountedPtr<ITensorList> tl = ...;
daliTensorList_h tl_handle = tl.release(); // C now owns it
// tl is now null, C code is responsible for calling daliTensorListDecRef
Derived-to-Base Conversion
// TensorWrapper<CPUBackend> derives from ITensor
RefCountedPtr<TensorWrapper<CPUBackend>> specific = Wrap(shared_tensor);
// Implicit conversion to base type
RefCountedPtr<ITensor> generic = std::move(specific);
// specific is now null, generic holds the pointer with same ref count