Question

Pointers vs Objects in C++: Why Use a Pointer Instead of the Object Itself?

cppc++11pointersc++-faq

Question

I am coming from a Java background and have started working with objects in C++. One thing I noticed is that people often use pointers to objects instead of creating the objects directly.

For example:

CopyObject* myObject = new CopyObject();

instead of:

CopyObject myObject;

And instead of calling a member function like this:

myObject.testFunc();

we write:

myObject->testFunc();

Why would we do it this way? I assume it has something to do with efficiency or speed because we get direct access to the memory address. Is that correct?

Short Answer

By the end of this page, you will understand the difference between storing an object directly and storing a pointer to an object in C++. You will learn when pointers are necessary, why they are not automatically faster, how stack and heap allocation differ, and why modern C++ usually prefers direct objects or smart pointers over raw new and raw pointers.

Concept

In C++, an object and a pointer to an object are two very different things.

  • CopyObject myObject; creates the object itself.
  • CopyObject* myObject = new CopyObject(); creates a pointer variable that stores the address of an object created elsewhere in memory.

The key idea

A pointer is useful when you need indirection: one variable refers to an object rather than containing it directly.

That matters in C++ because you often care about:

  • Object lifetime: how long the object should exist
  • Ownership: who is responsible for destroying it
  • Polymorphism: using a base-class pointer to refer to a derived object
  • Optional values: a pointer can be nullptr
  • Avoiding expensive copies: pass or store references/pointers instead of copying large objects

Why not always use pointers?

Because pointers add complexity:

  • You must ensure the pointer is valid
  • Raw pointers created with new must eventually be cleaned up with delete
  • Pointer syntax is harder to read
  • Extra indirection can be slower, not faster

In fact, for many cases, direct objects are the best choice.

Important modern C++ rule

In modern C++:

  • Prefer direct objects when possible
  • Prefer references when you just want to refer to an existing object
  • Prefer smart pointers (std::unique_ptr, std::shared_ptr) when dynamic allocation is needed
  • Avoid raw new unless you have a very specific reason

Stack vs heap

A direct object like this:

CopyObject myObject;

often lives in automatic storage duration (commonly called the stack, though the standard is more precise).

A dynamically allocated object like this:

CopyObject* myObject = new CopyObject();

creates the actual object in dynamic storage (commonly called the heap), and myObject only stores its address.

Heap allocation is useful when:

  • the object's lifetime must outlive the current scope
  • the size or number of objects is only known at runtime
  • polymorphism is needed

But heap allocation usually costs more than direct construction.

Efficiency misconception

Your guess about speed is a common beginner assumption, but usually it is not correct.

A pointer does not give "direct access" in a faster sense. It adds an extra step:

  1. Read the pointer value
  2. Follow it to the actual object
  3. Access the member

A direct object is often simpler and can be more cache-friendly.

So pointers are mainly about flexibility and lifetime management, not raw speed.

Mental Model

Think of it like this:

  • A direct object is like keeping a book on your desk.
  • A pointer is like keeping a note with the library shelf location of the book.

If the book is already on your desk, you can open it immediately. If you only have the location note, you must first go find the book.

So why keep the note instead of the book?

Because sometimes:

  • the book is too large to keep on your desk
  • several people need to refer to the same book
  • the book may change while the note still points to the current copy
  • the book might live somewhere beyond your room
  • the note can also be empty (nullptr) if there is no book yet

In C++, direct objects are often the simplest choice. Pointers are for when you need that extra flexibility.

Take Quiz

Syntax and Examples

Direct object

class CopyObject {
public:
    void testFunc() {
        std::cout << "Hello\n";
    }
};

int main() {
    CopyObject myObject;
    myObject.testFunc();
}

Here, myObject is the actual object.

  • Use . to access members
  • The object is destroyed automatically when it goes out of scope

Raw pointer to object

class CopyObject {
public:
    void testFunc() {
        std::cout << "Hello\n";
    }
};

int main() {
    CopyObject* myObject = new CopyObject();
    myObject->testFunc();
    delete myObject;
}

Here:

  • myObject is a pointer
  • new CopyObject() creates the object dynamically
  • Use -> to access members through a pointer
  • delete is required to avoid a memory leak

Better modern C++ alternative: smart pointer

#include <iostream>
#include <memory>

class CopyObject {
public:
    void testFunc() {
        std::cout << "Hello\n";
    }
};

int main() {
    auto myObject = std::make_unique<CopyObject>();
    myObject->testFunc();
}

This still uses a pointer-like object, but memory is cleaned up automatically.

Reference example

If you already have an object and just want another name for it:

CopyObject obj;
CopyObject& ref = obj;
ref.testFunc();

A reference is often a better fit than a pointer when null is not needed and the object must exist.

Polymorphism example

One major reason to use pointers or references is polymorphism:

#include <iostream>
#include <memory>

class Animal {
public:
    virtual void speak() {
        std::cout << "Animal sound\n";
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof\n";
    }
};

int main() {
    std::unique_ptr<Animal> pet = std::make_unique<Dog>();
    pet->speak();
}

This lets one variable refer to different derived object types at runtime.

Step by Step Execution

Consider this example:

#include <iostream>

class Counter {
public:
    int value;

    Counter() : value(0) {}

    void increment() {
        value++;
    }
};

int main() {
    Counter direct;
    direct.increment();

    Counter* ptr = new Counter();
    ptr->increment();

    std::cout << direct.value << "\n";
    std::cout << ptr->value << "\n";

    delete ptr;
}

What happens step by step

Counter direct;

  • A Counter object is created directly.
  • Its constructor runs.
  • value becomes 0.
  • direct is the object itself.

direct.increment();

  • The increment() function is called on the object.
  • value changes from 0 to 1.

Counter* ptr = new Counter();

  • A new Counter object is dynamically allocated.
  • Its constructor runs.
  • value becomes 0.
  • ptr stores the memory address of that object.
  • ptr is not the object itself.

ptr->increment();

  • -> means: go to the object that ptr points to, then access its member.
  • value changes from 0 to 1.

Output

Both lines print:

1
1

But the two objects are stored and managed differently.

delete ptr;

  • The dynamically allocated object is destroyed.
  • If you forget this when using raw new, you leak memory.

Important takeaway

Both styles can call the same member functions. The real difference is not what the object can do, but how it is stored, shared, and destroyed.

Real World Use Cases

When direct objects are commonly used

Local helper objects

std::string name = "Alice";
std::vector<int> numbers;

These are normal direct objects. They are simple, safe, and automatically cleaned up.

Temporary processing

FileParser parser;
parser.parse();

If the object only needs to exist inside the current scope, direct construction is usually best.

When pointers are useful

Polymorphic APIs

A graphics engine may store different shapes through a base type:

std::vector<std::unique_ptr<Shape>> shapes;

Each element may actually be a Circle, Rectangle, or Line.

Optional object presence

If an object may or may not exist:

User* currentUser = nullptr;

Or better in modern C++:

std::optional<User> currentUser;

Shared access to the same object

Several parts of a program may need to refer to the same resource.

std::shared_ptr<Config> config;

Large runtime-managed structures

Linked lists, trees, graphs, and many custom data structures rely on pointers because nodes refer to other nodes.

Objects that outlive a scope

A factory function may create an object and return ownership:

std::unique_ptr<Service> createService();

The object continues to live after the function returns.

Take Quiz

Real Codebase Usage

In real C++ codebases, developers usually avoid raw owning pointers and use clearer ownership patterns.

Common patterns

1. Prefer direct members

class Engine {
    Logger logger;
};

If an object must always exist as part of another object, store it directly.

2. Use references for required non-owning access

void process(const Config& config);

This says:

  • the object must exist
  • this function does not own it
  • no copy is made

3. Use pointers for optional or reseatable access

void process(const Config* config) {
    if (!config) return;
}

A pointer can be nullptr and can point somewhere else later.

4. Use std::unique_ptr for exclusive ownership

class App {
    std::unique_ptr<Database> db;
};

This makes ownership explicit.

5. Use std::shared_ptr only when ownership is genuinely shared

std::shared_ptr<Session> session;

This is useful, but should not be the default because shared ownership adds overhead and complexity.

Guard clauses and validation

Pointers are often checked before use:

void printUser(const User* user) {
    if (!user) {
        std::cout << "No user\n";
        return;
    }

    std::cout << user->name << "\n";
}

Container usage

Real code often stores objects directly when possible:

std::vector<Point> points;

and stores pointers only when necessary:

std::vector<std::unique_ptr<Widget>> widgets;

This often depends on whether polymorphism, stable addresses, or non-copyable behavior are needed.

Common Mistakes

1. Assuming pointers are automatically faster

This is one of the most common misconceptions.

Broken thinking:

// Not automatically more efficient just because it uses a pointer
MyClass* obj = new MyClass();

Why this is wrong:

  • new usually costs more than direct construction
  • pointer dereferencing adds indirection
  • memory locality can be worse

2. Forgetting to delete raw pointers

Broken code:

void f() {
    MyClass* obj = new MyClass();
    obj->testFunc();
}

Problem:

  • obj is never deleted
  • this leaks memory

Better:

void f() {
    auto obj = std::make_unique<MyClass>();
    obj->testFunc();
}

3. Using -> on an object instead of a pointer

Broken code:

MyClass obj;
obj->testFunc();

Fix:

obj.testFunc();

Rule:

  • use . with objects
  • use -> with pointers

4. Using . on a pointer

Broken code:

MyClass* obj = new MyClass();
obj.testFunc();

Fix:

obj->testFunc();
delete obj;

5. Dereferencing nullptr

Broken code:

MyClass* obj = nullptr;
obj->testFunc();

Problem:

  • undefined behavior, usually a crash

Safer:

if (obj) {
    obj->testFunc();
}

6. Using raw new when direct construction is enough

Overcomplicated:

MyClass* obj = new MyClass();
obj->testFunc();
delete obj;

Simpler:

MyClass obj;
obj.testFunc();

7. Confusing ownership with access

A pointer does not automatically mean ownership.

void useThing(Thing* thing); // may or may not own it; unclear

This ambiguity is why smart pointers and references are preferred when possible.

Comparisons

ConceptContains the object itself?Can be null?Needs manual delete?Good default?Typical use
Direct objectYesNoNoYesLocal variables, members that always exist
Reference (T&)NoNoNoOftenRequired non-owning access
Raw pointer (T*)NoYesSometimesUsually no for ownershipOptional access, low-level code, interop
std::unique_ptr<T>NoYesNoYes when dynamic ownership is neededExclusive ownership
std::shared_ptr<T>NoYesNoSometimesShared ownership

. vs ->

SyntaxUse when
obj.memberobj is an actual object
ptr->memberptr is a pointer to an object

Direct object vs pointer

Direct object

  • simpler
  • automatic lifetime management
  • often faster or at least no worse
  • best default in many cases

Pointer

  • useful for optional access
  • useful for polymorphism
  • useful when lifetime must be controlled separately
  • adds complexity and possible memory bugs
Take Quiz

Cheat Sheet

Core rule

  • T obj; creates the object itself
  • T* ptr; creates a pointer that can point to a T

Member access

obj.method();   // object
ptr->method();  // pointer

Dynamic allocation

T* ptr = new T();
delete ptr;

Prefer:

auto ptr = std::make_unique<T>();

When to use what

  • Use direct objects by default
  • Use references for non-owning required access
  • Use pointers for optional or reseatable access
  • Use smart pointers for dynamic ownership

Remember

  • Pointers are not automatically faster
  • Raw new usually means you must manage cleanup manually
  • nullptr means “points to nothing”
  • Dereferencing an invalid pointer causes undefined behavior

Quick examples

Person p;
p.sayHi();
Person* p = new Person();
p->sayHi();
delete p;
auto p = std::make_unique<Person>();
p->sayHi();

FAQ

Is using a pointer faster than using an object in C++?

Usually no. A pointer adds indirection, and dynamic allocation is often slower than creating an object directly.

Why do I need -> instead of . in C++?

Because . accesses a member on an object, while -> accesses a member through a pointer to an object.

When should I use a pointer in C++?

Use a pointer when you need optional access, dynamic lifetime, polymorphism, or a structure where objects refer to each other.

Should I use new for every object in C++?

No. In modern C++, most objects should be created directly or managed with smart pointers.

What is better: pointer or reference?

Use a reference when the object must exist and you just want another name for it. Use a pointer when null or reseating is needed.

Why is std::unique_ptr preferred over raw pointers?

It automatically destroys the owned object, making ownership clearer and preventing many memory leaks.

Can a pointer point to different objects over time?

Yes. A pointer can be reassigned to point somewhere else, which is one reason it is more flexible than a reference.

Are objects in C++ always on the stack?

No. Direct local objects commonly have automatic storage duration, but objects can also be static, dynamically allocated, or embedded inside other objects.

Take Quiz

Mini Project

Description

Build a small C++ program that demonstrates the difference between a direct object, a raw pointer, and a smart pointer. This project helps you see how creation, member access, and destruction differ between these approaches.

Goal

Create and use the same class in three ways, then observe how syntax and lifetime management change.

Requirements

  1. Create a class with a constructor, destructor, and one member function.
  2. Create one object directly and call its member function.
  3. Create one object with a raw pointer and call its member function.
  4. Create one object with std::unique_ptr and call its member function.
  5. Make sure the raw pointer object is properly deleted.
Take Quiz

Keep learning

Related questions

View more