#include <iostream>
#include <memory>
using namespace std;
Pointers are one of the most powerful, yet challenging to use, C++ capabilities.
Pointers also enable pass-by-reference and can be used to create and manipulate pointer-based dynamic data structures.
Pointers have relationship to built-in static arrays
array
and vector
objects to built-in arrays.A pointer contains the memory address of a variable that, in turn, contains a specific value.
In this sense, a variable name directly references a value, and a pointer indirectly references a value.
Referencing a value through a pointer is called indirection.
The declaration
int* countPtr, count; // both vars are int pointers
int *countPtr, count; // countPtr is a int pointers
declares the variable countPtr
to be a type int*
(i.e., a pointer to an int value) and is read (right to left), "countPtr is a pointer to int".
count
in the preceding declaration is declared to be an int
*
in the declaration applies only to countPtr
*
).When *
appears in a declaration, it is not an operator
Pointers can be declared to point to objects of any type.
*
prefixed to the name (with or without spaces in between).Pointers should be initialized to nullptr
(new in C++11) or to a memory either when they're declared or in an assignment.
A pointer with the value nullptr
"points to nothing" and is known as a null pointer.
Initialize all pointers to prevent pointing to unknown or uninitialized areas of memory.
In earlier versions of C++, the value specified for a null pointer was 0
or NULL
NULL
is defined in several standard library headers to represent the value 0
Initializing a pointer to NULL
is equivalent to initializing a pointer to 0
, but prior to C++11, 0
was used by convention.
The value 0
is the only integer value that can be assigned directly to a pointer variable without first casting the integer to a pointer type
&
and *
are used to create pointer values and "dereference" pointers, respectively.&
) Operator¶The address operator (&
) is a unary operator that obtains the memory address of its operand.
Assuming the declarations
{
int y{5}; // declare variable y
int* yPtr{nullptr}; // declare pointer variable
yPtr = &y; // assign address of y to yPtr
cout << "Pointer: " << yPtr << ", value: " << y << endl;
cout << "Address of yhe pointer: " << &yPtr;
}
Pointer: 0x7ffe4f52e8b4, value: 5 Address of yhe pointer: 0x7ffe4f52e8a8
&
) Operator (cont.)¶Representation in memory with integer variable y
stored at memory location 600000 and pointer variable yPtr
stored at location 500000.
The operand of the address operator must be an l-value - the address operator cannot be applied to constants or to expressions that result in temporary values (like the results of calculations).
*
) Operator¶The unary *
operator - commonly referred to as the indirection operator or dereferencing operator.
Returns an l-value representing the object to which its pointer operand points.
Called dereferencing a pointer
A dereferenced pointer may also be used as an l-value on the left side of an assignment.
Dereferencing an uninitialized or a null pointer results in undefined behavior.
{
int a{17};
int* aPtr = &a;
cout << "The address of `a` is " << &a << endl;
cout << "The value of `aPtr` is " << aPtr << endl;
cout << "The address of `aPtr` is " << &aPtr << endl;
cout << "The value of `a` is " << a << endl;
cout << "The value of `*aPtr` is " << *aPtr << endl;
*aPtr = 18; // *aPtr is l-value
cout << "The value of `a` is " << a << endl;
}
The address of `a` is 0x7ffe4f52e8b4 The value of `aPtr` is 0x7ffe4f52e8b4 The address of `aPtr` is 0x7ffe4f52e8a8 The value of `a` is 17 The value of `*aPtr` is 17 The value of `a` is 18
There are three ways in C++ to pass arguments to a function
pass-by-value
pass-by-reference with reference arguments
pass-by-reference with pointer arguments
Pointers can be used to modify one or more variables in the caller or to pass pointers to large data objects to avoid the overhead of copying the objects.
You can use pointers and the indirection operator (*
) to accomplish pass-by-reference.
When calling a function with an argument that should be modified, the address of the argument is passed.
// Pass-By-Value
int cubeByValue(int x) {
return x*x*x;
}
{
int number{8};
cout << "Original `num` value is " << number << endl;
number = cubeByValue(number);
cout << "New `num` value is " << number << endl;
}
Original `num` value is 8 New `num` value is 512
// Pass-By-Reference
void cubeByRef(int &xRef) {
xRef = xRef * xRef * xRef;
}
{
int number{5};
cout << "Original `number` value is " << number << endl;
cubeByRef(number);
cout << "New `number` value is " << number << endl;
}
Original `number` value is 5 New `number` value is 125
// Pass-By-Reference with Pointers
void cubeByRefPtr(int* xPtr) {
*xPtr = *xPtr * *xPtr * *xPtr;
}
{
int number{8};
cout << "Original `number` value is " << number << endl;
cubeByRefPtr(&number);
cout << "New `number` value is " << number << endl;
}
Original `number` value is 8 New `number` value is 512
Insight: All Arguments Are Passed By Value
Passing a variable by reference with a pointer does not actually pass anything by reference
The called function can then access that variable in the caller simply by dereferencing the pointer, thus accomplishing pass-by-reference.
type arrayName[arraySize];
arraySize
must be an integer constant greater than zero.{
// To reserve 5 elements for built-in array of ints named c
int c[5]{1,2,3,4,5};
cout << c[0] << endl; // zero-based indexing
cout << c[3] << endl;
}
1 4
arrayName
is implicitly convertible to &arrayName[0]
.const
to indicate that the elements should not be modified.{
int array[5]{ 9, 7, 5, 3, 1 };
// print address of the array's first element
std::cout << "Element 0 has address: " << &array[0] << '\n';
// print the value of the pointer the array decays to
std::cout << "The array decays to a pointer holding address: " << array << '\n';
}
Element 0 has address: 0x7ffe4f52e8a0 The array decays to a pointer holding address: 0x7ffe4f52e8a0
//void someFunc(int* array, size_t len)
void someFunc(int array[], size_t len) {
for (size_t i=0;i<len;++i)
array[i] = 0;
}
{
int array[5]{ 9, 7, 5, 3, 1 };
for (size_t i=0;i<5;++i) cout << array[i] << " ";
someFunc(array, 5); cout << endl;
for (size_t i=0;i<5;++i) cout << array[i] << " ";
}
9 7 5 3 1 0 0 0 0 0
++
)--
)+
or +=
)-
or -=
)int v[5]
has been declared and that its first element is at memory location 3000.vPtr
has been initialized to point to v[0]
(i.e., the value of vPtr is 3000).vPtr
can be initialized to point to v
with either of the following statementsvPtr+2
would produce 3008 (from the calculation 3000 + 2 * 4)v
, vPtr
would now point to v[2]
{
int v[5]{2,4,8,16,32};
int* vPtr{v}; //same as vPtr{&v[0]};
cout << "Ptr: " << vPtr << endl;
vPtr += 2;
cout << "Ptr: " << vPtr << ", value: "<< *vPtr;
}
Ptr: 0x7ffe4f52e8a0 Ptr: 0x7ffe4f52e8a8, value: 8
reinterpret_cast
) must be used to convert the value of the pointer on the right of the assignment to the pointer type on the left of the assignment.void*
without casting.void*
pointer cannot be dereferenced.sizeof
a string literal is the length of the string including the terminating null character.#include <cstring>
{
char name[]{ "John" }; // only use 5 characters (4 letters + null terminator)
cout << "My name is: " << name << endl;
cout << sizeof(name) << endl;
cout << strlen(name) << endl;
cout << name[0] << ", " << name[1] << ", " << name[2] << ", " << name[3] << endl;
cout << "There is a null terminator at the end: "<< (name[4] == 0) << endl;
}
My name is: John 5 4 J, o, h, n There is a null terminator at the end: 1
void someFunction()
{
// Resource is a struct or class
Resource *ptr = new Resource;
// do stuff with ptr here
delete ptr;
}
ptr
.void someFunction()
{
// Resource is a struct or class
Resource *ptr = new Resource;
// the function returns early, and ptr won’t be deleted!
if (x == 0)
return; // throw 0;
delete ptr;
}
void someFunction()
{
// Resource is a struct or class
Resource var;
// the function returns early, and ptr will be deleted!
if (x == 0)
throw 0;
} // destructor will be call at the end of the scope lifetime
ptr
.#include <memory>
void someFunction()
{
// Resource is a struct or class
std::auto_ptr<Resource> ptr = new Resource;
if (x == 0)
throw 0; // ptr will be deallocated here
return; // ptr will be deallocated here
}
class Resource {
public:
Resource *selfptr{nullptr};
Resource() {selfptr = this; cout << "Allocate the resource: "<< selfptr << endl; }
~Resource() { cout << "Deallocate the resource: " << selfptr << endl; selfptr = nullptr; }
}
{
Resource r; // implicit allocation
} // implicit deallocation at the end of the scope
Allocate the resource: 0x7ffe4f52e8b0 Deallocate the resource: 0x7ffe4f52e8b0
{
Resource* rptr = new Resource; // explicit allocation
throw 0; // problem here, destructor never called
delete rptr;
}
Allocate the resource: 0x561fdfe69840
Error:
my_auto_ptr
smart pointer¶// my_auto_ptr
template<typename T>
class my_auto_ptr {
T* ptr;
public:
my_auto_ptr(T* newptr) : ptr{newptr} {}
~my_auto_ptr() { delete ptr; }
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
};
{
Resource* rptr = new Resource; // explicit allocation
my_auto_ptr<Resource> smartptr{rptr}; // wrapped in auto_ptr
throw 0; // no problem here
delete rptr;
} // destructor for auto_ptr called here, and so for Resource
Allocate the resource: 0x561fe035a640 Deallocate the resource: 0x561fe035a640
Error:
std::auto_ptr
has a number of problems that makes using it dangerous.
std::auto_ptr
implements move semantics through the copy constructor and assignment operator, passing a std::auto_ptr
by value to a function will cause your resource to get moved to the function parameter
auto_ptr
argument from the caller (not realizing it was transferred and deleted)auto_ptr
always deletes its contents using non-array delete.
auto_ptr
won't work correctly with dynamically allocated arrays.auto_ptr
doesn't play nice with a lot of the other classes in the standard library
{
Resource* rptr = new Resource; // explicit allocation
cout << "Try to use my resource: " << rptr->selfptr << endl;
{
my_auto_ptr<Resource> smartptr{rptr}; // wrapped in the smart pointer
}
cout << "Try to use my resource: " << rptr->selfptr << endl; // different object
// delete rptr; // double delete
}
Allocate the resource: 0x561fdff3adb0 Try to use my resource: 0x561fdff3adb0 Deallocate the resource: 0x561fdff3adb0 Try to use my resource: 0x561fe061b8e0
std::unique_ptr
std::shared_ptr
std::weak_ptr
make_unique()
.unique_ptr
class.#include <memory>
class widget
{
private:
std::unique_ptr<int> data;
public:
widget(const int size) { data = std::make_unique<int>(size); }
void do_something() {}
};
void functionUsingWidget() {
widget w(1000000); // lifetime automatically tied to enclosing scope
// constructs w, including the w.data gadget member
// ...
w.do_something();
// ...
} // automatic destruction and deallocation for w and w.data
{
shared_ptr<Resource> rptr = make_shared<Resource>(); // explicit allocation
cout << "Try to use my resource: " << rptr->selfptr << endl;
{
shared_ptr<Resource> smartptr{rptr}; // wrapped in the smart pointer
cout << "Try to use my resource from `shared_ptr`: " << smartptr->selfptr << endl; // different object
}
cout << "Try to use my resource: " << rptr->selfptr << endl; // different object
// delete rptr; // double delete
}
Allocate the resource: 0x561fdfefd9e0 Try to use my resource: 0x561fdfefd9e0 Try to use my resource from `shared_ptr`: 0x561fdfefd9e0 Try to use my resource: 0x561fdfefd9e0 Deallocate the resource: 0x561fdfefd9e0