Question
C++ new Expression: Difference Between `new Test` and `new Test()`
Question
In C++, if Test is a normal class, is there any difference between these two expressions?
Test* test = new Test;
and:
Test* test = new Test();
I want to understand whether the parentheses after the type name change how the object is created or initialized.
Short Answer
By the end of this page, you will understand how new Type and new Type() behave in C++, especially for classes. You will learn the difference between default initialization and value initialization, why the distinction matters more for built-in types than for ordinary classes, and how this affects real code.
Concept
In C++, new does two things:
- allocates memory
- initializes an object in that memory
The important part here is how the object is initialized.
For class types
If Test is a regular class type, these are often equivalent:
Test* a = new Test;
Test* b = new Test();
In many ordinary cases, both expressions call the class's default constructor.
That is why for a typical class, you usually do not see a practical difference.
Why people still care about the difference
The difference becomes important because C++ has several initialization rules:
- default initialization:
new Test - value initialization:
new Test()
For class types, value initialization usually still ends up using the default constructor, so the result may look the same.
For non-class types such as int, the behavior is very different:
int* a = new int; // uninitialized
* b = ();
Mental Model
Think of new as renting a storage unit and then deciding what to place inside it.
new Testmeans: rent the space and initialize it normally for that typenew Test()means: rent the space and explicitly ask for value initialization
For a class, both often result in calling the same default constructor, so the storage unit ends up containing the same kind of object.
For a primitive type like int, the difference is bigger:
new int= you get a box with whatever was already therenew int()= you get a box reset to zero
So the parentheses are like saying, "Please initialize this in the safer, explicit way."
Syntax and Examples
Core syntax
Type* p1 = new Type;
Type* p2 = new Type();
Example with a class
#include <iostream>
using namespace std;
class Test {
public:
Test() {
cout << "Test constructor called\n";
}
};
int main() {
Test* a = new Test;
Test* b = new Test();
delete a;
delete b;
}
What happens?
Both lines create a Test object dynamically and both call the default constructor.
Example with a built-in type
#include <iostream>
using namespace std;
int main() {
* a = ;
* b = ();
cout << *b << ;
a;
b;
}
Step by Step Execution
Consider this code:
#include <iostream>
using namespace std;
class Test {
public:
Test() {
cout << "constructed\n";
}
};
int main() {
Test* p1 = new Test;
Test* p2 = new Test();
delete p1;
delete p2;
}
Step by step
Test* p1 = new Test;
- Memory is allocated for one
Testobject. Testis default-initialized.- Because
Testis a class with a default constructor, that constructor runs. p1stores the address of the new object.
Test* p2 = new Test();
- Memory is allocated for one
Testobject. Testis value-initialized.
Real World Use Cases
1. Creating objects dynamically
When you need an object to outlive the current scope, you may allocate it dynamically:
Test* service = new Test();
This was more common in older C++ code and still appears in legacy systems.
2. Initializing primitive data safely
If you dynamically allocate numbers, counters, or flags, initialization matters:
int* counter = new int();
This ensures the counter starts at 0.
3. Working with simple structs or data containers
struct Config {
int port;
bool debug;
};
Config* cfg = new Config();
This can zero-initialize members in useful cases, preventing garbage values.
4. Reading and maintaining older C++ code
You will often see both forms in real codebases. Knowing the difference helps you understand whether the author intended explicit initialization or not.
5. Resource-owning objects
Even though modern C++ prefers smart pointers, you may still see:
Real Codebase Usage
In real C++ projects, developers usually care less about the visual difference between new T and new T() for classes, and more about safe initialization and ownership.
Common patterns
Prefer automatic storage when possible
Test test;
This avoids manual memory management.
Prefer smart pointers over raw new
#include <memory>
auto test = std::make_unique<Test>();
This is safer because ownership is clear and memory is released automatically.
Use explicit initialization for simple data
auto count = std::make_unique<int>(0);
In modern code, developers prefer being explicit rather than relying on subtle initialization rules.
Validation and setup in constructors
For classes, the constructor often performs setup, validation, or resource acquisition. In those cases, both new Test and new Test() may behave the same because the constructor defines the object's initial state.
Common Mistakes
Mistake 1: Assuming the parentheses never matter
They often do not matter for ordinary classes, but they do matter for built-in types.
int* a = new int; // uninitialized
int* b = new int(); // zero-initialized
Avoid it
Use explicit initialization when you need a known starting value.
Mistake 2: Reading uninitialized memory
Broken code:
int* x = new int;
std::cout << *x << "\n"; // undefined behavior
Avoid it
Initialize the value:
int* x = new int();
or
int* x = new int(42);
Mistake 3: Forgetting that class members may still be uninitialized
Broken code:
Comparisons
| Expression | Initialization kind | Class type behavior | Built-in type behavior |
|---|---|---|---|
new T | Default initialization | Usually calls default constructor | Uninitialized |
new T() | Value initialization | Usually calls default constructor | Zero-initialized |
new T(args) | Direct initialization | Calls matching constructor | Initializes with given value |
new Test vs new Test()
For an ordinary class with a default constructor:
Test* a = new Test;
Test* b = new Test();
Cheat Sheet
Quick reference
T* p = new T; // default initialization
T* p = new T(); // value initialization
T* p = new T(42); // direct initialization
Main rule
- For many ordinary classes,
new Tandnew T()behave the same. - For built-in types, they can behave very differently.
Examples
int* a = new int; // uninitialized
int* b = new int(); // 0
class Test {
public:
Test() {}
};
Test* a = new Test; // calls default constructor
Test* b = new Test(); // also calls default constructor
Safer modern alternative
FAQ
Does new Test() always call the constructor?
For a class type with a default constructor, yes, it usually calls that constructor.
Is new Test the same as new Test() for every type?
No. They are often equivalent for ordinary classes, but not for built-in types like int.
What is the difference between default initialization and value initialization?
Default initialization uses the type's default behavior. Value initialization performs a more explicit initialization and often zero-initializes simple types.
Why does new int() give 0?
Because value initialization of a built-in numeric type zero-initializes it.
Should I use raw new in modern C++?
Usually no. Prefer std::make_unique or std::make_shared unless you have a specific reason.
Can class members still be uninitialized even if I create the object with new?
Yes. If the class does not initialize its members, some members may still have indeterminate values depending on the initialization form and class design.
Which form should I prefer when reading old code?
Understand the intent first. For classes, both may be fine. For primitive or simple data types, often signals intentional initialization.
Mini Project
Description
Build a small C++ program that compares dynamic initialization forms for a class and a built-in type. This helps you see when parentheses matter and when they do not.
Goal
Create a program that demonstrates the difference between new T and new T() using both a class and an int.
Requirements
- Define a simple class with a default constructor that prints a message.
- Dynamically allocate one object with
new Testand another withnew Test(). - Dynamically allocate an
intwithnew int()and print its value. - Clean up all dynamically allocated memory with
delete. - Add comments explaining which initialization form each line uses.
Keep learning
Related questions
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Base Class Constructor Rules Explained
Learn how C++ base class constructors are called from derived classes, including order, syntax, defaults, and common mistakes.
C++ Casts Explained: C-Style Cast vs static_cast vs dynamic_cast
Learn the difference between C-style casts, static_cast, and dynamic_cast in C++ with clear examples, safety rules, and real usage tips.