Memory Leaks
When a pointer and local variables go out of scope, the dynamically allocated memory created with new remains inaccessible but never gets deallocated. This persistent consumption of memory is known as a memory leak.
How shared_ptr Works
The use_count() method tracks how many shared_ptr instances reference the same memory block. When you call reset() on a shared_ptr, it releases ownership of the currant object—the pointer stops pointing to that object and automatically becomes nullptr. Once all pointers to a memory block are destroyed (or reset), the underlying object is automatically deallocated.
When a function returns, any shared_ptr instances defined within that function scope are automatically destroyed, which means the memory they manage gets freed. In contrast, raw pointers declared in the same scope do not trigger any cleanup of their pointed-to memory.
Creating shared_ptr Instances
Use the shared_ptr template to declare a smart pointer and make_shared to allocate memory in a single operation. The managed memory is automatically released when the last shared_ptr pointing to it is destroyed or reset.
The recommended approach involves using make_shared directly rather than separate declarations and allocations.
Custom Deleters
You can provide a custom cleanup function when constructing a shared_ptr. This deleter executes whenever the pointer loses its last reference or when reset() is called.
In this example, close_file receives the raw pointer fp as its argument when invoked:
void close_file(FILE* fp)
{
if (fp == nullptr) return;
fclose(fp);
cout << "File closed" << endl;
}
int main()
{
FILE* file_handle = fopen("C:\\Users\\WENCHAO\\Desktop\\data.txt", "w");
shared_ptr<FILE> safe_ptr{ file_handle, close_file };
safe_ptr.reset();
if (safe_ptr == nullptr)
{
cout << "Error" << endl;
}
else
{
cout << "Open" << endl;
}
system("pause");
return 0;
}
Accessing Raw Pointers
When you need the raw pointer from a shared_ptr (for example, to pass it to a legacy function expecting a raw pointer), use the get() method. This allows coexistence between smart and raw pointer usage.
Shared Ownership with Aliasing
A shared_ptr can be constructed as an alias to another shared_ptr, pointing to a different address within the same object or to a member of the managed object. This is useful when you need separate ownership semantics for a subobject.
The reset() method on an aliasing shared_ptr only affects that specific alias—it does not affect the lifetime of the original managed object.
Consider this scenario with nested objects:
class Inner
{
public:
Inner() {
cout << "Inner constructed" << endl;
}
~Inner() {
cout << "Inner destroyed" << endl;
}
int value1 = 0;
};
class Container
{
public:
Container() {
cout << "Container constructed" << endl;
}
~Container() {
cout << "Container destroyed" << endl;
}
Inner nested;
int value2 = 0;
int value3 = 0;
};
int main()
{
shared_ptr<Container> primary;
primary = make_shared<Container>();
shared_ptr<Inner> alias_ptr(primary, &(primary->nested));
alias_ptr.reset();
shared_ptr<Inner> alias_ptr2(primary, &(primary->nested));
system("pause");
return 0;
}
Cyclic Reference Prevention
When two objects reference each other through shared_ptr, a cyclic dependency forms. The owning shared_ptr cannot release its object becuase the other shared_ptr holds a reference, and vice versa. This prevents both objects from being destroyed, causing a memory leak. To break such cycles, use weak_ptr for one side of the relationship when you need a non-owning reference.