Object Lifecycle
How objects are created, owned, shared, and destroyed — without garbage collection.
No Garbage Collector
Unlike .NET, sharp-runtime has no garbage collector. System::GC is a stub class
that exists only for API compatibility — it does nothing. All memory management is explicit C++ RAII.
This is a fundamental difference from .NET. You must think about ownership when writing or porting code.
Ownership Patterns
| Pattern | Use when | Notes |
|---|---|---|
| Stack value | Short-lived, single owner, value semantics | Destroyed at scope end |
std::unique_ptr<T> | Single owner, heap-allocated | Ownership transferable via move |
std::shared_ptr<T> | Shared ownership, multiple holders | Ref-counted; cycles are a risk |
| Raw pointer / reference | Borrowed (non-owning) | Caller must keep owner alive |
Typical Object Creation
// Stack allocation (value type behavior)
System::DateTime dt = System::DateTime::Now();
// Heap with unique ownership
auto stream = std::make_unique<System::IO::MemoryStream>();
// Heap with shared ownership
auto obj = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ref2 = obj; // both hold a reference
// obj and ref2 are both destroyed when count drops to 0
Virtual Destructor Rule
All classes inheriting from System::Object get a virtual destructor via
virtual ~Object() = default. This ensures the correct destructor runs when
deleting through a base pointer.
std::unique_ptr<System::Object> obj = std::make_unique<MyDerivedClass>();
// When obj goes out of scope, ~MyDerivedClass() runs correctly
IDisposable Pattern
Classes with external resources (file handles, sockets, etc.) implement
System::IDisposable with a Dispose() method. Unlike C# using
statements, there is no language-level RAII integration — you must call Dispose()
manually or use a custom RAII wrapper.
// Manual dispose
auto fs = std::make_unique<System::IO::FileStream>("file.txt", ...);
// ... use ...
fs->Dispose(); // or let unique_ptr destructor run (if it calls Dispose)
Dispose(); finalizers are a GC fallback.
In sharp-runtime, the C++ destructor is the primary cleanup mechanism. Dispose()
exists for API compatibility and may delegate to the destructor.
Copy and Move Semantics
sharp-runtime classes use default C++ copy/move semantics unless a class explicitly manages resources:
- Value-type-like classes (e.g.,
DateTime,TimeSpan,Vector2) — copyable by value, cheap to copy. - Collection types (e.g.,
List<T>) — hold astd::vectorinternally; copy is a deep copy of the vector. - Stream types — typically non-copyable; move-only via
unique_ptr. - Shared objects — use
shared_ptr; copying the pointer is cheap (increments refcount).
Null Handling
In C++ there is no concept of "null object" for value types. Reference semantics are represented by pointers:
- Raw pointer can be
nullptr std::shared_ptr<T>can be empty (default-constructed = null)std::unique_ptr<T>can be null- Use
Nullable<T>for value types that need a null state
Reference Cycles
shared_ptr to B and B holds a shared_ptr back to A,
neither will ever be destroyed. Break cycles with std::weak_ptr<T>.
This is a common pitfall when porting C# code where the GC handles cycles automatically.