ACF $AcfVersion:0$
istd - Standard Utilities Library

Introduction

The istd library provides foundational utilities and interfaces used throughout the ACF framework. It includes standard patterns, helper classes, and core interfaces that form the basis for other ACF components.

Key Features

  • Change Notification: Observer pattern implementation with granular change tracking
  • Smart Pointers: Interface-based smart pointer templates for safe memory management
  • Utilities: Bit manipulation, CRC calculation, and other common utilities
  • Interface Definitions: Base interfaces for changeable objects and type information
  • Event Handling: Event-based notification and change delegation
  • Type System: Version information and type identification support

Architecture

The istd library is organized into several key components:

Change Notification

Smart Pointers

The istd library provides specialized smart pointers for interface-based polymorphic objects that support automatic memory management with different ownership semantics:

Smart Pointer Features

The interface-based smart pointers (TUniqueInterfacePtr and TSharedInterfacePtr) provide:

  • Separation between root object and interface pointer for polymorphic designs
  • Type-safe dynamic casting to derived interfaces
  • Integration with istd::IPolymorphic base class
  • Move semantics for efficient ownership transfer
  • Conversion between unique and shared ownership

Smart Pointer Comparison

Feature TUniqueInterfacePtr TSharedInterfacePtr TOptInterfacePtr
Ownership Unique (exclusive) Shared (reference counted) Optional (managed or unmanaged)
Copy No (move only) Yes Yes
Thread-safe N/A Reference counting only Reference counting only
Use case Single owner Multiple owners Mixed ownership scenarios

Utilities

  • istd::CBitManip: Bit manipulation utilities
  • istd::CCrc: CRC calculation for data integrity
  • istd::CFastBinaryIndex: Fast binary indexing for sorted data
  • istd::CRandomGenerator: Random number generation

Version Information

  • istd::IVersionInfo: Interface for version information
  • istd::CVersionInfo: Version information implementation

Usage Examples

Change Notification Example

class MyModel : public istd::IChangeable
{
public:
static const ChangeFlag CF_VALUE_CHANGED = MakeChangeFlag(0);
void SetValue(int value)
{
if (m_value != value)
{
istd::CChangeNotifier notifier(this, &CF_VALUE_CHANGED);
m_value = value;
}
}
private:
int m_value = 0;
};
Help class which provides the automatic update mechanism of the model.
Common interface for data model objects, which can be changed.
Definition IChangeable.h:28

Smart Pointer Examples

Unique Interface Pointer

TUniqueInterfacePtr provides exclusive ownership - only one pointer can own the object at a time.

// Create a unique pointer - takes ownership
istd::TUniqueInterfacePtr<IMyInterface> uniquePtr = CreateMyObject();
// Use the pointer
uniquePtr->DoSomething();
if (uniquePtr.IsValid())
{
uniquePtr->ProcessData();
}
// Transfer ownership via move
istd::TUniqueInterfacePtr<IMyInterface> ptr2 = std::move(uniquePtr);
// uniquePtr is now invalid, ptr2 owns the object
// Extract raw pointer and release ownership
IMyInterface* rawPtr = ptr2.PopInterfacePtr();
// ptr2 is now invalid, caller must delete rawPtr
bool IsValid() const noexcept
Unique ownership smart pointer for interface types.
InterfaceType * PopInterfacePtr() noexcept
Intelligent pop of interface pointer.

Shared Interface Pointer

TSharedInterfacePtr provides shared ownership - multiple pointers can share ownership of the same object. The object is automatically deleted when the last owner is destroyed.

// Create a shared pointer
istd::TSharedInterfacePtr<IMyInterface> sharedPtr1 = CreateMyObject();
// Share ownership by copying
// Both sharedPtr1 and sharedPtr2 now own the object
// Use either pointer
sharedPtr1->DoSomething();
sharedPtr2->ProcessData();
// Object is deleted when both pointers go out of scope
Shared ownership smart pointer for interface types.

Converting Unique to Shared Ownership

One of the most common patterns is converting from unique ownership (single owner) to shared ownership (multiple owners). This is a one-way conversion - once converted to shared, you cannot convert back to unique.

// Start with unique ownership
istd::TUniqueInterfacePtr<IMyInterface> uniquePtr = CreateMyObject();
uniquePtr->Initialize();
// Method 1: Direct conversion using constructor (transfers ownership)
istd::TSharedInterfacePtr<IMyInterface> sharedPtr1(std::move(uniquePtr));
// uniquePtr is now invalid, sharedPtr1 owns the object
// Method 2: Using FromUnique() method
istd::TUniqueInterfacePtr<IMyInterface> uniquePtr2 = CreateAnotherObject();
sharedPtr2.FromUnique(uniquePtr2);
// uniquePtr2 is now invalid, sharedPtr2 owns the object
// Method 3: Using CreateFromUnique() static method
istd::TUniqueInterfacePtr<IMyInterface> uniquePtr3 = CreateThirdObject();
// uniquePtr3 is now invalid, sharedPtr3 owns the object
// Now you can share ownership
// Both sharedPtr3 and sharedPtr4 own the object
static TSharedInterfacePtr CreateFromUnique(TUniqueInterfacePtr< OtherInterface > &uniquePtr) noexcept
Create a shared pointer from unique pointer of other type by transferring ownership if dynamic_cast s...
TSharedInterfacePtr & FromUnique(TUniqueInterfacePtr< DerivedType > &&uniquePtr) noexcept
Convert from unique to shared.

Converting with Dynamic Casting

Derived-to-base conversion (upcast) does not need a dynamic cast. It is supported directly by constructors and assignment operators.

Base-to-derived conversion (downcast) requires an explicit dynamic-cast helper:

  • Use MoveCastedPtr() for TUniqueInterfacePtr
  • Use SetCastedPtr() or dynamicCast() for TSharedInterfacePtr

Shared setup for the examples below:

// Interfaces
class IBase : public istd::IPolymorphic { /*...*/ };
class IDerived : public IBase { /*...*/ };
Base interface for all used interfaces and implementations.

Upcast with TUniqueInterfacePtr (derived to base):

// Direct upcast via move assignment
istd::TUniqueInterfacePtr<IDerived> derivedPtr = CreateDerivedObject();
istd::TUniqueInterfacePtr<IBase> basePtr(std::move(derivedPtr));
// basePtr now owns the object, derivedPtr is invalid

Downcast with TUniqueInterfacePtr (base to derived):

istd::TUniqueInterfacePtr<IBase> basePtr = CreateMaybeDerivedObject();
if (derivedPtr.MoveCastedPtr(std::move(basePtr)))
{
// Success: derivedPtr now owns the object, basePtr is invalid
derivedPtr->DoDerivedOperation();
}
else
{
// Failed: dynamic_cast failed
}
bool MoveCastedPtr(TUniqueInterfacePtr< SourceInterfaceType > &&source) noexcept

Upcast to TSharedInterfacePtr (derived to base):

istd::TUniqueInterfacePtr<IDerived> derivedPtr = CreateDerivedObject();
basePtr = std::move(derivedPtr);
// basePtr now owns the object, derivedPtr is invalid

Downcast with TSharedInterfacePtr (base to derived):

istd::TSharedInterfacePtr<IBase> basePtr = CreateMaybeDerivedSharedObject();
if (sharedDerived.SetCastedPtr(basePtr))
{
// Success: sharedDerived and basePtr now share ownership
sharedDerived->DoDerivedOperation();
}
bool SetCastedPtr(TSharedInterfacePtr< SourceInterfaceType > &source) noexcept
Set from another shared pointer if dynamic_cast succeeds.

Optional Interface Pointer

TOptInterfacePtr can hold either managed (owned) or unmanaged (borrowed) pointers, providing flexibility in ownership semantics.

class DataProcessor
{
public:
// Accept owned pointer
void SetOwnedSource(istd::TSharedInterfacePtr<IDataSource> source)
{
m_dataSource.SetManagedPtr(source);
}
// Accept borrowed pointer (caller retains ownership)
void SetBorrowedSource(IDataSource* source)
{
m_dataSource.SetUnmanagedPtr(source);
}
// Convert from unique to managed
void TakeOwnership(istd::TUniqueInterfacePtr<IDataSource>& source)
{
m_dataSource.TakeOver(source);
// source is now invalid, m_dataSource owns it
}
// Check ownership status
void Process()
{
if (m_dataSource.IsValid())
{
if (m_dataSource.IsManaged())
{
// We own this pointer
qDebug() << "Processing owned data source";
}
else
{
// We don't own this pointer
qDebug() << "Processing borrowed data source";
}
m_dataSource->ReadData();
}
}
};
// Usage example
void Example()
{
DataProcessor processor;
// Case 1: Owned pointer
istd::TSharedInterfacePtr<IDataSource> ownedSource = CreateDataSource();
processor.SetOwnedSource(ownedSource);
// Both processor and ownedSource share ownership
// Case 2: Borrowed pointer
IDataSource* borrowedSource = GetGlobalDataSource();
processor.SetBorrowedSource(borrowedSource);
// Caller must ensure borrowedSource remains valid
// Case 3: Transfer from unique
istd::TUniqueInterfacePtr<IDataSource> uniqueSource = CreateDataSource();
processor.TakeOwnership(uniqueSource);
// uniqueSource is now invalid, processor owns it
}
A wrapper for managed and unmanaged interface pointers.
void TakeOver(TOptInterfacePtr &ptr)
void SetManagedPtr(SharedInterfacePtr managedPtr)
void SetUnmanagedPtr(Interface *ptr)

Factory Pattern with Smart Pointers

A common pattern is to use unique pointers in factory methods and convert to shared pointers where needed:

class IProcessor : public istd::IPolymorphic
{
public:
virtual void Process() = 0;
};
// Factory returns unique pointer (single owner initially)
istd::TUniqueInterfacePtr<IProcessor> CreateProcessor(const QString& type)
{
if (type == "fast")
return istd::TUniqueInterfacePtr<IProcessor>(new FastProcessor());
else if (type == "accurate")
return istd::TUniqueInterfacePtr<IProcessor>(new AccurateProcessor());
else
}
// Manager class needs to share processors
class ProcessorManager
{
QList<istd::TSharedInterfacePtr<IProcessor>> m_processors;
public:
void AddProcessor(const QString& type)
{
// Create with unique ownership
istd::TUniqueInterfacePtr<IProcessor> processor = CreateProcessor(type);
if (processor.IsValid())
{
// Convert to shared for storage
istd::TSharedInterfacePtr<IProcessor> sharedProc(std::move(processor));
m_processors.append(sharedProc);
}
}
void ProcessAll()
{
for (auto& processor : m_processors)
{
processor->Process();
}
}
// Return shared pointer to allow external sharing
istd::TSharedInterfacePtr<IProcessor> GetProcessor(int index)
{
if (index >= 0 && index < m_processors.size())
return m_processors[index];
}
};

CRC Calculation Example

#include <istd/CCrc.h>
QByteArray data = "Hello, World!";
quint32 crc = istd::CCrc::GetCrcFromData(
reinterpret_cast<const quint8*>(data.data()),
data.size()
);

Thread Safety

The istd library components are generally not thread-safe by default. When using across multiple threads, appropriate synchronization mechanisms should be employed by the application.

Dependencies

The istd library has minimal dependencies:

  • Qt Core: QString, QByteArray, QList, and other core classes
  • Standard C++ library

Best Practices

  1. Use Smart Pointers: Prefer smart pointers over raw pointers for automatic memory management
  2. Choose the Right Smart Pointer:
    • Use TUniqueInterfacePtr when ownership is clear and exclusive
    • Use TSharedInterfacePtr when multiple owners need to share the object
    • Use TOptInterfacePtr when you need to mix owned and borrowed pointers
  3. Factory Pattern: Return TUniqueInterfacePtr from factory methods, convert to TSharedInterfacePtr if sharing is needed
  4. Conversions: Convert from unique to shared ownership when needed, but remember this is one-way
  5. Avoid Premature Sharing: Start with unique ownership and only convert to shared when actually needed
  6. Scope Change Notifications: Use CChangeNotifier for automatic notification on scope exit
  7. Group Related Changes: Use CChangeGroup to batch multiple changes into one notification
  8. Check Change Flags: Always check specific change flags rather than relying on "any change"
  9. Version Compatibility: Use IVersionInfo for version checking when serializing data

See Also