Memory Management
RAII, ownership rules, no garbage collector, smart pointers, and lifetime patterns.
System::GC class exists as a stub/no-op compatibility header only.
Core Principle: RAII
All resource management in sharp-runtime follows C++ RAII (Resource Acquisition Is Initialization): resources are acquired in constructors and released in destructors. When a value goes out of scope or a smart pointer's reference count drops to zero, its destructor runs automatically.
This differs fundamentally from .NET, where the garbage collector handles deallocation.
Ownership Rules
| Pattern | C++ Tool | When to Use |
|---|---|---|
| Exclusive ownership | std::unique_ptr<T> | One clear owner; ownership can be transferred |
| Shared ownership | std::shared_ptr<T> | Multiple owners (e.g., immutable collections) |
| Non-owning reference | raw pointer T* or reference T& | Observing only; caller guarantees lifetime |
| Value semantics | by-value (stack) | Most primitive types, structs |
Immutable Collections
Immutable collection types use shared_ptr<const std::container<T>>:
// ImmutableArray<T> is backed by shared_ptr to const vector
auto arr = ImmutableArray<int>::Create({1, 2, 3});
No GC — Practical Consequences
- No cycles via naked pointers. Use
std::weak_ptrto break ownership cycles. - Object lifetime is deterministic. Destructors run immediately when scope exits.
- No finalizers. Use RAII destructors instead of
~Object()finalizers. - IDisposable pattern:
System::IDisposableexists but there is nousingstatement. CallDispose()explicitly or use RAII wrappers.
Null Handling
C# references can be null; C++ pointers can be nullptr.
In sharp-runtime code:
- Raw pointer parameters that can be null are typically
const Object*— callers can passnullptr. System::Nullable<T>(backed bystd::optional) is used for nullable value types.- Dereferencing a null pointer is undefined behaviour — no NullReferenceException is automatically thrown. Sharp-runtime may throw
System::NullReferenceExceptionwhere defensive checks exist.
Copy and Move Semantics
Most value types in sharp-runtime (structs, primitives) follow standard C++ copy/move semantics.
Large types like System::Text::StringBuilder store an internal std::string buffer and
support move construction and assignment.
Class types that inherit from System::Object typically have virtual destructors.
They are generally heap-allocated and accessed via pointer or smart pointer.
Destructor and Cleanup Patterns
// Pattern 1: RAII — stack-allocated value type
{
System::Text::StringBuilder sb;
sb.Append("hello");
// sb is destroyed when it goes out of scope
}
// Pattern 2: shared_ptr for heap-allocated Objects
auto stream = std::make_shared<System::IO::MemoryStream>();
stream->Write(buffer, 0, len);
// Destroyed when all shared_ptrs go out of scope
// Pattern 3: unique_ptr for exclusive ownership
auto reader = std::make_unique<System::IO::BinaryReader>(stream);
// Destroyed when reader goes out of scope
Differences from .NET / C# Behavior
| C# / .NET | sharp-runtime / C++ |
|---|---|
| GC collects unreachable objects | No GC — RAII + smart pointers |
| Finalizers run asynchronously | Destructors run synchronously at scope exit |
using statement calls Dispose() | No using — call Dispose() manually or use RAII |
| Reference cycles handled by GC | Use std::weak_ptr to break cycles |
| All objects are heap-allocated | Value types can be stack-allocated |
Null reference throws NullReferenceException | Null dereference is UB — some places throw explicitly |
Known Risks
- Dangling pointers when raw pointer observers outlive the owning object
- Double-free if ownership is transferred without clearing the source pointer
- Reference cycles via
shared_ptr— useweak_ptr - Use-after-free when passing raw
thisto background threads (fixed inThreading::Timerviashared_ptr<State>)