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
{
public:
static const ChangeFlag CF_VALUE_CHANGED = MakeChangeFlag(0);
void SetValue(int value)
{
if (m_value != value)
{
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.
Smart Pointer Examples
Unique Interface Pointer
TUniqueInterfacePtr provides exclusive ownership - only one pointer can own the object at a time.
uniquePtr->DoSomething();
{
uniquePtr->ProcessData();
}
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.
sharedPtr1->DoSomething();
sharedPtr2->ProcessData();
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.
uniquePtr->Initialize();
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:
class IDerived : public IBase { };
Base interface for all used interfaces and implementations.
Upcast with TUniqueInterfacePtr (derived to base):
Downcast with TUniqueInterfacePtr (base to derived):
{
derivedPtr->DoDerivedOperation();
}
else
{
}
bool MoveCastedPtr(TUniqueInterfacePtr< SourceInterfaceType > &&source) noexcept
Upcast to TSharedInterfacePtr (derived to base):
basePtr = std::move(derivedPtr);
Downcast with TSharedInterfacePtr (base to derived):
{
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:
{
}
void SetBorrowedSource(IDataSource* source)
{
}
{
}
void Process()
{
{
{
qDebug() << "Processing owned data source";
}
else
{
qDebug() << "Processing borrowed data source";
}
m_dataSource->ReadData();
}
}
};
void Example()
{
DataProcessor processor;
processor.SetOwnedSource(ownedSource);
IDataSource* borrowedSource = GetGlobalDataSource();
processor.SetBorrowedSource(borrowedSource);
processor.TakeOwnership(uniqueSource);
}
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:
{
public:
virtual void Process() = 0;
};
{
if (type == "fast")
else if (type == "accurate")
else
}
class ProcessorManager
{
QList<istd::TSharedInterfacePtr<IProcessor>> m_processors;
public:
void AddProcessor(const QString& type)
{
{
m_processors.append(sharedProc);
}
}
void ProcessAll()
{
for (auto& processor : m_processors)
{
processor->Process();
}
}
{
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
- Use Smart Pointers: Prefer smart pointers over raw pointers for automatic memory management
- 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
- Factory Pattern: Return TUniqueInterfacePtr from factory methods, convert to TSharedInterfacePtr if sharing is needed
- Conversions: Convert from unique to shared ownership when needed, but remember this is one-way
- Avoid Premature Sharing: Start with unique ownership and only convert to shared when actually needed
- Scope Change Notifications: Use CChangeNotifier for automatic notification on scope exit
- Group Related Changes: Use CChangeGroup to batch multiple changes into one notification
- Check Change Flags: Always check specific change flags rather than relying on "any change"
- Version Compatibility: Use IVersionInfo for version checking when serializing data
See Also