Your browser doesn't support JavaScript Advanced Pointers – Windows Programming

Advanced Pointers

Pointers provide a way of working with the memory addresses of variables and objects. So far, pointers have been introduced in their simplest form. This section builds on that knowledge by looking at how pointers can be used with objects and dynamically allocated memory.

C++ allows objects to be created with automatic storage duration which means it is destroyed automatically when it reaches the end of its scope, as with:

MyClass obj;

It also allows objects to be created dynamically on the free store using the new operator:

MyClass* ptr = new MyClass;

In this case, ptr is a pointer to the dynamically allocated object. The object remains in memory until it is explicitly destroyed using the delete operator:

delete ptr;

Pointers to objects are particularly important when working with inheritance and polymorphism.

#include <iostream>

class SimpleObject
{
public:
    SimpleObject();
    ~SimpleObject();

private:
    int itsValue;
};

SimpleObject::SimpleObject()
{
    std::cout << "Constructor called.\n";
    itsValue = 1;
}

SimpleObject::~SimpleObject()
{
    std::cout << "Destructor called.\n";
}

int main()
{
    std::cout << "SimpleObject object...\n";
    SimpleObject object1;

    std::cout << "SimpleObject* pObject = new SimpleObject...\n";
    SimpleObject* pObject = new SimpleObject;

    std::cout << "delete pObject...\n";
    delete pObject;

    std::cout << "Exiting, watch object1 go...\n";

    return 0;
}

Accessing Data Members Using Pointers

Data members and member functions are accessed using the dot (.) operator when working with objects created locally. For example:

SimpleObject object1;object1.someFunction();

When an object is created dynamically, the pointer returned by new can be used to access the object. The pointer must first be dereferenced to obtain the object it points to, after which the dot operator can be used:

(*pObject).someFunction();

The parentheses are necessary because the dot operator has a higher precedence than the dereference operator. Without the parentheses, the expression would be interpreted incorrectly.

Because (*pObject).someFunction() requires the pointer to be explicitly dereferenced before the member can be accessed, C++ provides the arrow operator (->) as a simpler way to access members through a pointer.

The previous expression can therefore be written as:

pObject->someFunction();

The arrow operator can be used to access both data members and member functions through a pointer to an object.

In the worked example below, the SimpleObject class contains an integer data member called itsValue. The constructor initialises itsValue to 5 and then raised it to 10. The member functions GetValue() and SetValue() are then accessed through the pointer using the arrow operator (->).

The object is eventually destroyed using:

delete pObject;

This calls the SimpleObject destructor and releases the memory allocated for the object on the free store. The destructor in this example has an empty body because the class does not allocate any additional dynamic memory that needs to be released.

#include <iostream>

class SimpleObject
{
public:
    SimpleObject() { itsValue = 5; }
    ~SimpleObject() {}

    int GetValue() const { return itsValue; }
    void SetValue(int value) { itsValue = value; }

private:
    int itsValue;
};

int main()
{
    SimpleObject* pObject = new SimpleObject;

    std::cout << "Object value is "
              << pObject->GetValue()
              << "\n";

    pObject->SetValue(10);

    std::cout << "Object value is "
              << pObject->GetValue()
              << "\n";

    delete pObject;

    return 0;
}

The this Pointer

In C++, the this pointer is a special pointer available inside non-static member functions. It points to the object on which the function was called.

For example, if an object called object1 calls a member function:

object1.GetValue();

the member function has access to a this pointer that points to object1.

Normally, you do not need to use the this pointer explicitly. For example, within a member function, you can simply write:

itsValue = 5;

The compiler understands this as referring to the itsValue member belonging to the current object.

You can, however, explicitly use the this pointer:

this->itsValue = 5;

Here, this points to the current object, and the arrow operator (->) is used to access its itsValue data member.

The this pointer can be used to access members of the current object, including data members and member functions. It is particularly useful when a member function needs to refer explicitly to the current object, such as when returning a pointer to that object or when distinguishing between a data member and a function parameter with the same name.

#include <iostream>
class SimpleObject
{
public:
    SimpleObject(int value)
    {
        this->value = value;
    }

    void Display()
    {
        std::cout << "Value: " << this->value << "\n";
    }

private:
    int value;
};

int main()
{
    SimpleObject object1(10);

    object1.Display();

    return 0;
}

SimpleObject(int value)
{
    this->value = value;
}

Why use the this Pointer?

The this pointer is provided automatically by C++, but in most cases you do not need to use it explicitly. It becomes useful when you need to refer specifically to the current object, or when a function parameter has the same name as a data member.

The this pointer is also a pointer, which means it contains the memory address of the current object. This allows it to be used wherever a pointer to an object is required.

For example, a member function can return this to provide a pointer to the object on which the function was called:

SimpleObject* GetObject()
{
return this;
}

This can be useful when an object needs to be passed to another function or when several operations need to be performed on the same object.

Although most member functions have no need to use this explicitly, understanding that it is a pointer to the current object becomes important when working with pointers to objects, inheritance and more advanced C++ techniques.

Stray or Dangling Pointers

A common source of difficult-to-find bugs is a dangling pointer. A dangling pointer is a pointer that contains the address of an object that has already been destroyed or whose memory is no longer available to the program.

For example:

SimpleObject* pObject = new SimpleObject;

delete pObject;

After delete is executed, the SimpleObject has been destroyed and the memory that was allocated for it has been released. However, pObject still contains the address where the object used to be located. The pointer is now a dangling pointer.

If the program subsequently tries to use it:

pObject->GetValue();

The result is undefined behaviour. The memory may have been reused for something else, and the program might crash, produce unexpected results, or appear to work normally before failing later.

A simple way to prevent the pointer from being accidentally used after the object has been deleted is to assign nullptr to it:

delete pObject; pObject = nullptr;

Const Pointers

The const keyword can be used with pointers to control whether the object being pointed to, the pointer itself, or both can be changed.

There are three common forms:

const int* pOne; int* const pTwo; const int* const pThree;

pOne is a pointer to a const int. The pointer itself can be changed to point to another integer, but the value being pointed to cannot be changed through pOne.

Therefore, this is not allowed:

*pOne = 5;

However, the pointer itself can be changed:

pOne = &x;

pTwo is a const pointer to an int. The pointer cannot be changed to point to another object, but the value being pointed to can be changed:

*pTwo = 5;

The following is not allowed because pTwo is a const pointer:

pTwo = &x;

pThree is a const pointer to a const int. Neither the pointer nor the value it points to can be changed through the pointer:

*pThree = 5; // Not allowed
pThree = &x; // Not allowed

A useful way to remember the difference is to ask what is const:

const int* p1; // the int is const int* const p2; // the pointer is const const int* const p3; // both are const

In the first declaration, the value cannot be changed through the pointer. In the second, the pointer cannot be changed. In the third, neither can be changed.

Const Pointers and Class Objects

const can also be used with pointers to class objects. A pointer to a const object can only call member functions that are declared const.

const SimpleObject* pObject = &object;

The pointer can be changed to point to another object, but the object cannot be changed through the pointer.

A const pointer is different:

SimpleObject* const pObject = &object;

The pointer must always point to the same object, but the object’s data can be changed through the pointer.

For example:

pObject->SetValue(20); // Allowed with a const pointer

If both the pointer and object are const:

const SimpleObject* const pObject = &object;

neither the pointer nor the object can be changed through the pointer.

#include <iostream>

class SimpleObject
{
public:
    SimpleObject(int value) : value(value) {}

    int GetValue() const
    {
        return value;
    }

    void SetValue(int newValue)
    {
        value = newValue;
    }

private:
    int value;
};

int main()
{
    SimpleObject object1(10);
    SimpleObject object2(20);

    // Pointer to a const object
    const SimpleObject* p1 = &object1;

    std::cout << "Pointer to const object: "
              << p1->GetValue() << std::endl;

    p1 = &object2;   // Allowed
    // p1->SetValue(30);  // Not allowed


    // Const pointer to an object
    SimpleObject* const p2 = &object1;

    p2->SetValue(30);    // Allowed
    // p2 = &object2;     // Not allowed

    std::cout << "Const pointer to object: "
              << p2->GetValue() << std::endl;


    // Const pointer to a const object
    const SimpleObject* const p3 = &object1;

    std::cout << "Const pointer to const object: "
              << p3->GetValue() << std::endl;

    // p3->SetValue(40);  // Not allowed
    // p3 = &object2;     // Not allowed

    return 0;
}

Const Member Functions

A member function can be declared const by placing the const keyword after the function’s parameter list.

int GetValue() const { return value; }

A const member function promises not to change the object’s data members. This means it can be called for both normal objects and const objects.

int GetValue() const const; SimpleObject object(10); std::cout << object.GetValue();

For example:

This is allowed because GetValue() is a const member function. A non-const member function such as SetValue() cannot be called for a const object because it could change the object.

Const member functions are particularly useful when working with pointers or references to const objects. They allow information to be read without allowing the object to be modified.