What are Smart Pointers in C++?
We'll utilize smart pointers to save up memory for resources that aren't being used. Create a class containing a pointer, destructors, and overloaded operators (->, *). When an object's scope expires, the destructor will be invoked automatically, and the dynamically allocated memory will be erased.
Example
C++
#include <iostream>
using namespace std;
class smartPointer { // class to implement smart Pointer
int* ptr; // Actual pointer
public:
// Explicit constructor
explicit smartPointer(int* x = NULL) {
ptr = x;
}
// Destructor
~smartPointer() {
delete (ptr);
}
// Overloading dereferencing operator
int& operator*() {
return *ptr;
}
};
int main()
{
smartPointer p(new int());
*p = 10;
cout << *p;
return 0;
}
You can also try this code with Online C++ Compiler
Run Code
Output:
10
Try and compile with online c++ compiler.
The preceding example only applies to integers. We'll make a template that works with any data type.
Program:
C++
#include <iostream>
using namespace std;
template <class T> // template class
class SmartPtr {
T* smartPointer; // Actual pointer
public:
// Constructor
explicit SmartPtr(T* p = NULL) {
smartPointer = p;
}
// Destructor
~SmartPtr() {
delete (smartPointer);
}
// Overloading dereferncing operator
T& operator*() {
return *smartPointer;
}
T* operator->() {
return smartPointer;
}
};
int main()
{
SmartPtr<int> p(new int());
*p = 50;
cout << *p;
return 0;
}
You can also try this code with Online C++ Compiler
Run Code
Output:
50
How to Use Smart Pointers?
Smart pointers are a feature in C++ that provides automatic memory management for dynamically allocated objects. They are designed to help prevent common memory-related bugs, such as memory leaks and dangling pointers, by automatically deallocating memory when it is no longer needed. Let's discuss how you can use smart pointers in your C++ code:
1. Include the appropriate header
To use smart pointers, you need to include the appropriate header file. For example, to use `unique_ptr`, include `<memory>`.
2. Choose the appropriate smart pointer type
C++ provides three main types of smart pointers: `unique_ptr`, `shared_ptr`, and `weak_ptr`. Choose the one that best fits your needs:
- unique_ptr: Owns and manages the lifetime of a single object. It cannot be copied but can be moved.
- shared_ptr: Allows multiple pointers to share ownership of an object. The object is destroyed when the last `shared_ptr` goes out of scope.
- weak_ptr: Observes an object managed by a `shared_ptr` without owning it. It helps break circular dependencies and prevents memory leaks.
3. Create a smart pointer object
To create a smart pointer object, use the `make_unique`, `make_shared`, or `make_shared` functions, depending on the smart pointer type. For example:
auto ptr = std::make_unique<MyClass>(/* constructor arguments */);
4. Access the managed object
To access the object managed by a smart pointer, use the `->` operator or the `*` operator. For example:
ptr->someMethod();
(*ptr).someMethod();
5. Pass smart pointers as function parameters
When passing smart pointers to functions, consider using references or const references to avoid unnecessary copying. For example:
void processObject(const std::unique_ptr<MyClass>& ptr);
6. Avoid manually deleting the managed object
Smart pointers automatically delete the managed object when the smart pointer goes out of scope or when the last `shared_ptr` is destroyed. Avoid manually calling `delete` on the managed object, as it can lead to undefined behavior.
7. Be cautious with circular references
When using `shared_ptr`, be cautious of circular references, where objects hold `shared_ptr` to each other. This can prevent the objects from being destroyed, leading to memory leaks. Use `weak_ptr` to break circular references.
Let's see an example that shows the usage of `unique_ptr`:
C++
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass(int value) : value_(value) {
std::cout << "Constructor called" << std::endl;
}
~MyClass() {
std::cout << "Destructor called" << std::endl;
}
void printValue() {
std::cout << "Value: " << value_ << std::endl;
}
private:
int value_;
};
int main() {
auto ptr = std::make_unique<MyClass>(42);
ptr->printValue();
// Destructor called automatically when ptr goes out of scope
return 0;
}
You can also try this code with Online C++ Compiler
Run Code
Output:
Constructor called
Value: 42
Destructor called
In this example, a `unique_ptr` is created to manage an instance of `MyClass`. The `printValue` method is called using the `->` operator. When `ptr` goes out of scope at the end of the `main` function, the destructor of `MyClass` is automatically called, cleaning up the memory.
Note: Smart pointers provide a safer and more convenient way to manage dynamically allocated objects in C++. They help prevent memory leaks and make resource management more robust, leading to more reliable and maintainable code.
Types of Smart Pointers
Unique_ptr
A single item is stored in this sort of object. The current object is deallocated to attach a new one.
Program:
C++
#include <iostream>
#include <memory>
using namespace std;
class Cuboid { // Create the class
int length; // length of Cuboid
int breadth; // breadth of Cuboid
int height;
public:
Cuboid(int l, int b, int h) // parameterised constructor
{
length = l;
breadth = b;
height = h;
}
int volume() //Volume
{
return length * breadth * height;
}
};
int main()
{
unique_ptr<Cuboid> cu1(new Cuboid(20, 5, 6));
cout << cu1->volume() << endl; // This will print the volume
unique_ptr<Cuboid> cu2;
cu2 = move(cu1);
// This will print the volume
cout << cu2->volume() << endl;
return 0;
}
You can also try this code with Online C++ Compiler
Run Code
Output:
600
600
Shared_ptr
More than one object can point to a single pointer in shared_ptr simultaneously. The use_count() function maintains a reference counter for designating the object.
Program:
C++
#include <iostream>
#include <memory>
using namespace std;
class Cuboid { // Create the class
int length; // length of Cuboid
int breadth; // breadth of Cuboid
int height; // height of Cuboid
public:
Cuboid(int l, int b, int h) // parameterised constructor
{
length = l;
breadth = b;
height = h;
}
int volume()
{ // calculate volume of Cuboid
return length * breadth * height; // return volume
}
};
int main()
{
shared_ptr<Cuboid> cu1(new Cuboid(20, 5)); // create shared //ptr cu1
// volume will be printed
cout << cu1->volume() << endl;
// Create shared ptr cu2
shared_ptr<Cuboid> cu2;
cu2 = cu1;
cout << cu2->volume() << endl;
cout << cu1->volume() << endl;
cout << cu1.use_count() << endl;
return 0;
}
You can also try this code with Online C++ Compiler
Run Code
Output:
600
600
600
2
Weak_ptr
The shared pointer is identical to weak_ptr. The distinction is that it doesn't keep a reference counter, and the object doesn't have a tight grip on the pointer. This attribute may cause a deadlock if many objects attempt to hold the reference.
Guidelines to Follow When Working with Smart Pointers in C++:
1. Use the appropriate smart pointer type
C++ offers three main types of smart pointers: `unique_ptr`, `shared_ptr`, and `weak_ptr`. Choose the one that best suits your needs. Use `unique_ptr` when you want exclusive ownership of a resource, `shared_ptr` when you need shared ownership, and `weak_ptr` when you want to observe an object without owning it. Consider the ownership semantics and lifetime requirements of your objects when selecting the appropriate smart pointer type.
2. Prefer `make_unique` and `make_shared` for creating smart pointers
When creating smart pointers, prefer using the `make_unique` and `make_shared` functions instead of directly using the `new` keyword. These functions provide exception safety and can optimize memory allocation by combining the allocation of the object and the smart pointer in a single step. For example, use `auto ptr = std::make_unique<MyClass>(/* arguments */);` instead of `std::unique_ptr<MyClass> ptr(new MyClass(/* arguments */));`.
3. Use smart pointers as function parameters and return types
When passing ownership of objects between functions, consider using smart pointers as function parameters and return types. This makes the ownership transfer explicit and helps prevent resource leaks. For functions that don't take ownership, pass smart pointers by reference or const reference to avoid unnecessary copying. When returning a dynamically allocated object from a function, consider returning a smart pointer to ensure proper resource management.
4. Avoid manually deleting the managed object
Smart pointers automatically manage the lifetime of the objects they own. They deallocate the memory when the smart pointer goes out of scope or when the last `shared_ptr` is destroyed. Avoid manually calling `delete` on the managed object, as it can lead to undefined behavior and resource leaks. Trust the smart pointers to handle the deallocation for you.
5. Be cautious with circular references
When using `shared_ptr`, be aware of the possibility of circular references. Circular references occur when objects hold `shared_ptr` to each other, preventing them from being destroyed and leading to memory leaks. To break circular references, use `weak_ptr` for non-owning references. A `weak_ptr` allows you to observe an object without owning it, and it doesn't prevent the object from being destroyed when all `shared_ptr` owners are gone.
6. Use `unique_ptr` as the default choice
Unless you have specific reasons to use `shared_ptr` or `weak_ptr`, prefer using `unique_ptr` as your default choice for dynamically allocated objects. `unique_ptr` has minimal overhead and provides exclusive ownership semantics. It ensures that only one smart pointer owns the object at a time, preventing unexpected ownership transfers. Use `shared_ptr` only when you need shared ownership or when multiple parts of your code need to access the same object concurrently.
7. Avoid using smart pointers with arrays
Smart pointers are designed to manage single objects, not arrays. If you need to manage dynamically allocated arrays, consider using container classes like `std::vector` or `std::array` instead. If you must use a smart pointer with an array, use `std::unique_ptr<T[]>` and specify the array deleter. However, it's generally recommended to use container classes for better safety and convenience.
Summary
To wrap off this essay, I'll provide you with rules to follow while dealing with SMART pointers.
- Whenever possible, attempt to utilize smart pointers. If you're not dealing with several pointers/threads sharing a memory address, use unique_ptr in most circumstances.
- When dealing with numerous owners, utilize the reference-counted shared_ptr instead.
- Use a weak_ptr if you wish to explore an object without requiring that the object exists. This pointer is useful for such jobs since it does not add to the reference count.
- To decrease the overhead of utilizing smart pointers, make sure you only use raw pointers in a tiny amount of code or when you must use them.
Advantages of Smart Pointers
- Automatic Memory Management: Smart pointers automatically manage memory, reducing the risk of memory leaks and manual deallocation errors.
- Exception Safety: They ensure resources are released even in the case of exceptions, improving program reliability.
- Ownership Control: Smart pointers like unique_ptr, shared_ptr, and weak_ptr provide clear ownership semantics, making code easier to understand and maintain.
- RAII Compliance: They follow the Resource Acquisition Is Initialization (RAII) principle, enabling better resource management.
- Thread-Safe Reference Counting: shared_ptr offers thread-safe reference counting, allowing safe usage in multi-threaded applications.
Disadvantages of Smart Pointers
- Overhead: Smart pointers can introduce performance overhead due to additional memory and CPU usage for reference counting.
- Complexity: Misusing smart pointers, such as creating cyclic references with shared_ptr, can lead to memory leaks.
- Compatibility Issues: Some legacy codebases or libraries may not integrate well with smart pointers.
- Learning Curve: Understanding the nuances and appropriate use cases for different types of smart pointers can be challenging for beginners.
- Debugging Challenges: Debugging issues related to smart pointers, especially in complex programs, can sometimes be difficult.
Frequently Asked Questions
Does C++ 11 have smart pointers?
Yes, C++11 introduces smart pointers such as std::unique_ptr, std::shared_ptr, and std::weak_ptr in the standard library.
Are smart pointers memory safe?
Smart pointers are memory-safe as they automate resource management, reduce the likelihood of memory leaks, and help manage the lifecycles of objects correctly.
Is Unique Pointer a Smart Pointer?
Yes, unique_ptr is a type of smart pointer in C++. It ensures exclusive ownership of a resource, meaning no two unique_ptr instances can own the same resource, and it automatically deallocates memory when it goes out of scope.
Conclusion
Smart pointers in C++ are a powerful feature that simplify memory management and improve program safety by preventing memory leaks and dangling pointers. By leveraging types like unique_ptr, shared_ptr, and weak_ptr, developers can efficiently manage resource ownership and ensure exception-safe code.