int x = 5;
)new
/delete
(C++) or malloc
/free
(C)double* recordedTimes
declares a double pointer named recordedTimes. When a pointer is initialized with the address of a dynamically allocated array, the pointer "points to" the array.
new double[3]
dynamically allocates a double array with three elements.
malloc
/* Typically in header file */ typedef struct { int x; int y; } Point; /* Typically in c file */ Point* p = (Point*)malloc(sizeof(Point)); // Allocate enough space for Point p->x = 10; // Manual initialization required! p->y = 20; ... free(p); // Don’t forget to deallocate!
new
// Typically in header file class Point { public: int x = 0; int y = 0; }; // Typically in cpp file // Allocate and initialize Point* p = new Point; // Constructor initializes x/y to 0 ... // Deallocate delete p;
new
calls the constructor; malloc
does not.delete
calls the destructor; free
does not./* Typically in header file */ typedef struct{ int X; int Y; } Point; /* Typically in c file */ const int ArraySize = 15; /* Allocate enough space for Point array */ Point* Ptr = (Point*)malloc(sizeof(Point) * ArraySize); /* Initialize */ for(int Index = 0; Index < ArraySize; Index++){ Ptr[Index].X = 0; Ptr[Index].Y = 0; } ... /* Deallocate */ free(Ptr);
new[]
// Typically in header file class Point{ private: int X = 0; int Y = 0; public: }; //Typically in cpp file const int ArraySize = 15; // Allocate and initialize Point* Ptr = new Point[ArraySize]; ... // Deallocate delete[] Ptr;
delete
instead of delete[]
corrupts memory.delete Ptr;
→ Only the first element is destroyed!
int main() { int numOfSprints; double sampleData[3]; double* actualData; cin >> numOfSprints; actualData = new int[numOfSprints]; }
actualData
. Double pointer actualData points to an array dynamically allocated using the new operator. Thus, the array pointed to by actualData must be deallocated. As a rule of thumb, every array allocated using new should be deallocated using delete[].
#include <iostream> using namespace std; int main() { int numMinutes; double* bodyTemperatures; int i; cin >> numMinutes; bodyTemperatures = new double[numMinutes + 1]; for (i = 0; i <= numMinutes; ++i) { cin >> bodyTemperatures[i]; } cout << "Initial body temperature: " << bodyTemperatures[0] << "F" << endl; for (i = 1; i <= numMinutes; ++i) { cout << "Body temperature after " << i << " minute(s) of exercise: " << bodyTemperatures[i] << "F "; cout << "Change: " << bodyTemperatures[i] - bodyTemperatures[i - 1] << "F" << endl; } delete[] bodyTemperatures; return 0; }
When a pointer is declared, the pointer variable holds an unknown address until the pointer is initialized. A programmer may wish to indicate that a pointer points to "nothing" by initializing a pointer to null. Null means "nothing". A pointer that is assigned with the keyword nullptr is said to be null.
For functions with pointer parameters, a good practice is to check that a pointer is not null before accessing the memory pointed to by the pointer. In the following example, the PrintArray() function ensures that arrayPtr is not null before accessing elements in the array.void PrintArray(int* arrayPtr, int arraySize) { int i; if (arrayPtr != nullptr) { for (i = 0; i < arraySize - 1; ++i) { cout << arrayPtr[i] << ", "; } cout << arrayPtr[arraySize - 1] << endl; } } int main() { int* heartBeats = nullptr; int numHeartBeats; ... PrintArray(heartBeats, numHeartBeats); ... delete[] heartBeats; return 0;
std::vector<int> VectorOfInt;
std::vector<T>
– Similar to a dynamic array
std::tuple<T>
– Used for creating a tuple
std::set<T>
– Used for a set
std::map<K,V>
– Similar to a python dictionary, but a ordered
std::unordered_map<K,V>
– Similar to a python dictionary, usually implemented as hash table
// Dynamic array (like ArrayList in Java) std::vector<std::string> names; names.push_back("Alice"); names.push_back("Bob"); // Key-value store (ordered by keys) std::map<std::string, int> ages; ages["Alice"] = 30; ages["Bob"] = 25; // Fast lookup (hash table) std::unordered_map<std::string, int> wordCount; wordCount["hello"]++;
new
/delete
).std::vector
uses contiguous memory).A memory leak occurs when memory is allocated on the heap but not freed before the last reference to it is lost. Over time, this can lead to excessive memory usage and performance degradation.
A dangling reference occurs when a reference to memory is invalid because the memory has already been freed. This can lead to program crashes or undefined behavior if the system has repurposed the memory.
int main() {
MyClass* ptrOne = new MyClass;
MyClass* ptrTwo = new MyClass;
ptrOne = ptrTwo;
return 0;
}
class MyClass {
public:
MyClass() {
subObject = new int;
*subObject = 0;
}
~MyClass() {
delete subObject;
}
private:
int* subObject;
};
int main() {
MyClass* ptrOne = new MyClass;
MyClass* ptrTwo = new MyClass;
...
delete ptrOne;
ptrOne = ptrTwo;
return 0;
}
#include <memory> class Sleep{ public: Sleep(); ~Sleep(); void Set(int hoursVal, int minutesVal); void Print(); private: int hours; int minutes; }; ... void RunSleep(int hoursVal, int minutesVal) { unique_ptr<Sleep> sleepRecord(new Sleep()); sleepRecord->Print(); ... }
unique_ptr
)A unique_ptr
is a smart pointer that has sole ownership of the dynamically allocated object. It ensures that the memory is freed when the unique_ptr
goes out of scope.
unique_ptr
can own an object at a time.std::move()
.unique_ptr
is destroyed.make_unique
(C++14)The preferred way to create an object managed by a unique_ptr
is to use std::make_unique
. This prevents issues like manual memory allocation errors and provides better performance.
void foo(){
std::unique_ptr<int> APtr = std::make_unique<int>(3);
std::unique_ptr<int> BPtr;
// The following line prints out 3
std::cout << (*APtr) << std::endl;
// The following line hands over ownership to BPtr
BPtr = std::move(APtr);
}
What's wrong with this code?
std::unique_ptr<int> a = std::make_unique<int>(5);
std::unique_ptr<int> b = a;
Unique pointers cannot be copied! Use std::move()
to transfer ownership:
b = std::move(a);
shared_ptr
)A shared_ptr
allows multiple references to a dynamically allocated object. It uses reference counting to keep track of ownership, and when the reference count reaches zero, the memory is automatically freed.
make_shared
The preferred way to create an object managed by a shared_ptr
is to use std::make_shared
. This reduces overhead by allocating control block and object in a single operation, compared to std::shared_ptr
.
void foo(){
std::shared_ptr<int> APtr = std::make_shared<int>(3);
std::shared_ptr<int> BPtr;
// The following line prints out 3
std::cout << (*APtr) << std::endl;
// BPtr has a copy of the reference
BPtr = APtr;
// At end of foo the int will be freed because ref
// count goes to zero
}
What is the reference count to the float of 3.14f at each point?
auto a = std::make_shared<float>(3.14f); // Count: 1
{
auto b = a; // Count: ?
auto c = b; // Count: ?
} // Count: ?
Counts: 1 → 2 → 3 → 1
weak_ptr
)A weak_ptr
is similar to a shared_ptr
, but it does not contribute to the reference count. It is primarily used to prevent cyclic dependencies.
shared_ptr
before use.lock()
The lock()
function converts a weak_ptr
to a shared_ptr
. If the object is already freed, it returns a nullptr
.
std::weak_ptr<int> WPtr;
void foo(){
// Has to be copied into a shared_ptr before usage
if(auto SPt = WPtr.lock()){
// Access to pointer through SPt
}
else{
// Failed to gain access
}
}
{
auto SPtr = std::make_shared<int>(42);
WPtr = SPtr;
foo();
}
foo(); // WPtr will not be able to access through lock
What is wrong with the following code?
#include <iostream>
#include <memory>
struct Child;
struct Parent {
std::shared_ptr<Child> child;
~Parent() { std::cout << "Parent destroyed\n"; }
};
struct Child {
std::shared_ptr<Parent> parent;
~Child() { std::cout << "Child destroyed\n"; }
};
int main() {
auto p = std::make_shared<Parent>();
auto c = std::make_shared<Child>();
p->child = c;
c->parent = p;
}
p
and c
share ownership of each other.
When main()
ends, the reference count of both objects never reaches 0, which prevents the objects from being deallocated, resulting in memory leaks.
this
Pointer – enable_shared_from_this
Don’t create a new shared_ptr
from the this
raw pointer, it will create a new reference count, need to use shared_from_this()
.
enable_shared_from_this
– Template class that will allow creating a shared_ptr
from this
raw pointer
shared_from_this()
– Returns a shared_ptr
to the object that is calling the function
this
pointers.
class C1 : public std::enable_shared_from_this<C1> {
private:
int Val;
public:
int foo();
int bar(std::shared_ptr<C1> ptr);
};
int C1::foo(){
return Val * bar(shared_from_this());
}
int C1::bar(std::shared_ptr<C1> ptr){
return ptr->Val + 3;
}
PIMPL – Pointer to IMPLimentation idiom, hides the private implementation of the class by only showing a pointer and forward declaration of implementation class (or struct).
// Typically in header file
class ClassName{
private:
struct Implementation; // Forward declaration
std::unique PtrImpl;
public:
ClassName();
~ClassName();
void foo(const std::string &str);
};
// Typcially in cpp file
struct ClassName::Implementation{
std::string Name;
};
ClassName::ClassName(){
PtrImpl = std::make_unique();
}
ClassName::~ClassName(){
}
void ClassName::foo(const std::string &str){
PtrImpl->Name = str;
}
Pointer Type | Features | When to use |
---|---|---|
unique_ptr | A unique_ptr only allows an exclusive ownership of the object. The object's ownership can be transferred to a different unique_ptr, but not shared. When a unique_ptr goes out of scope, the object owned by the unique_ptr is deleted. | As an efficient replacement of raw pointers |
shared_ptr | A shared_ptr permits shared ownership of an object. When the last owner of the object goes out of scope, the object is deleted. Internally, a counter, called the reference count, keeps track of the number of owners sharing an object. | When a dynamically allocated object is shared by multiple pointers |
weak_ptr | A weak_ptr allows access to, but not ownership of, an object that is owned by a shared_ptr. | To interact with a dynamically allocated object whose memory is managed elsewhere |
std::vector
instead of new[]
.std::string
instead of char*
.delete
).delete
).delete
twice).new
/delete
for dynamic objects, but prefer containers.Louie 2.8 Fila 3.6 Sam 3.3
Students: Louie (2.8) Fila (3.6) Sam (3.3) Average GPA: 3.233
#include <iostream> #include <iomanip> #include "Student.h" using namespace std; int main() { string name1, name2, name3; double gpa1, gpa2, gpa3, aveGpa; Student *stdn1, *stdn2, *stdn3; // Read input cin >> name1; cin >> gpa1; cin >> name2; cin >> gpa2; cin >> name3; cin >> gpa3; // TODO: Dynamically allocate Student objects // TODO: Calculate average GPA // Output results cout << "Students:" << endl; stdn1->PrintInfo(); stdn2->PrintInfo(); stdn3->PrintInfo(); cout << endl; cout << fixed << setprecision(3); cout << "Average GPA: " << aveGpa << endl; // Deallocate memory delete stdn1; delete stdn2; delete stdn3; return 0; }
Ryley 3.84 Jane 3.99 Mcauley 3.67
Ryley added: Louie (2.80) Fila (3.60) Sam (3.30) Alex (1.00) Ryley (3.84) Jane added: Louie (2.80) Fila (3.60) Sam (3.30) Alex (1.00) Ryley (3.84) Jane (3.99) Mcauley added: Louie (2.80) Fila (3.60) Sam (3.30) Alex (1.00) Ryley (3.84) Jane (3.99) Mcauley (3.67)
#include <iostream> #include <iomanip> #include "Student.h" using namespace std; // Add a new student to an existing roster Student* AddStudent(Student* sdList, int listSize, string sdName, double sdGpa) { //TODO: Type your code here. return sdList; } // Output student info void PrintRoster(Student* sdList, int listSize) { for(int i = 0; i < listSize; i++) { sdList[i].PrintInfo(); } cout << endl; } int main() { string name1, name2, name3; double gpa1, gpa2, gpa3; Student* roster; int classSize = 4; roster = new Student[classSize]; roster[0] = Student("Louie", 2.8); roster[1] = Student("Fila", 3.6); roster[2] = Student("Sam", 3.3); roster[3] = Student(); // Using default name and gpa // Read input cin >> name1; cin >> gpa1; cin >> name2; cin >> gpa2; cin >> name3; cin >> gpa3; cout << fixed << setprecision(2); // Add first student roster = AddStudent(roster, classSize, name1, gpa1); ++classSize; cout << name1 << " added:" << endl; PrintRoster(roster, classSize); // Add second student roster = AddStudent(roster, classSize, name2, gpa2); ++classSize; cout << name2 << " added:" << endl; PrintRoster(roster, classSize); // Add third student roster = AddStudent(roster, classSize, name3, gpa3); ++classSize; cout << name3 << " added:" << endl; PrintRoster(roster, classSize); delete[] roster; return 0; }
Mad Libs are activities that have a person provide various words, which are then used to complete a short story in unexpected (and hopefully funny) ways.
Use the pointers declared to complete a program that outputs a short story based on four input values from a user (one integer and three strings). Because pointers are used, memory must be allocated before the input values are stored. Observe the output of the original cout statements (the input values are not correctly printed, why?) Fix the cout statements so that the correct values are printed.
Ex: If the input is:
5 Taylor tigers mall
the output is:
Taylor saw 5 different colors of tigers at the mall.
Fill in the code below:
#include <iostream>
#include <string>
using namespace std;
int main() {
int* wholeNumber;
string* firstName;
string* pluralAnimal;
string* genericPlace;
wholeNumber = new int;
// TODO: Allocate memory for the other pointers
// TODO: Store the input values in the allocated memory
// FIXME: Output the correct values
cout << firstName << " saw " << wholeNumber << " different colors of " << pluralAnimal;
cout << " at the " << genericPlace << "." << endl;
// Deallocate memory
delete firstName;
delete wholeNumber;
delete pluralAnimal;
delete genericPlace;
return 0;
}