Skip to main content
GameDev.net gamedev.net
🔒 Locked

MyClass myClass VS myClass = new MyClass()

Started by noodleBowl Nov 20, 2015 at 2:39 PM 16 replies 12.3k views
Original Post
noodleBowl
noodleBowl

I was wondering if someone could tell me the difference between:


MyClass class;

And


MyClass *myClass;
myClass = new MyClass();

Other than the second block of code is making a dynamic memory allocation and that I must delete it when I'm done with it otherwise I'll suffer memory leaks.

But what I mean is which one should I be using the most? Does it matter even?

Would it be bad to have all of my custom classes created using the dynamic memory way?

Juliean
Juliean




But what I mean is which one should I be using the most? Does it matter even?

Would it be bad to have all of my custom classes created using the dynamic memory way?

What you should use depends on the situation.

The first example creates the class on the stack. If you leave the scope of the function, it will be destroyed. If you have it as a class member, it will be destroyed with the owning class.

The second example creates the class on the heap. It will be located at a different memory location than in the first example. This has a few effects. First of all as you mentioned it isn't destroyed automatically. This must not be bad - now the class can live way beyond the scope it is created.
I would still recommend never using new where you can avoid it and instead look into the smart pointers - std::unique_ptr at least, which solves the problem with the memory leaks without additional cost, by taking care that "delete" is called automatically when the pointer leaves scope. You'd need c++11 though and look into move semantics, but it definately pays of.


std::unique_ptr<MyClass> myClass;
myClass = std::make_unique<MyClass>();

There are a few more differences between the two things:

- Stack space is limited (a few MB I belive?). If you create everything on the stack, you'll run out of memory very very soon

- If you allocate on stack, you have to know the size upfront. You cannot have dynamic arrays on the stack, you need to new[] there

- You cannot really have polymorphism if you do everything on the stack. Well technically, but... for example, if you have a class with virtual functions that is to inherited by other classes, and you want to store all those objects in a generic container, you have to use a pointer, otherwise it will slice off the derived class part.


class PolymorpicClass
{
    virtual void Foo(void) { exit() };
}

PolymorphicClass class; // you cannot store any derived class like this...
class.Foo(); // always calls "exit()"
PolymorphicClass* pClass; // ... but now you can

- Another kind of important point is performance. If you allocate a variable on the heap, it might lie in a totally different part of memory, meaning that you have an additional memory access (which can be very slow due to how RAM and cache works). Don't get overly paranoid with that though, there is nothing wrong with dynamically allocating memory. Just know that it is exactly the same speedwise.

As a rule of thumb, always allocate on the stack if you can (without making using your code hard than it has to be) and if you aren't in danger of running out of memory. Otherwise, use smart pointers instead of "new".

BitMaster
BitMaster
In addition to what Juliean said, I see no reason to write code like that anymore. Why not start like this:

std::unique_ptr<MyClass> myClass;
// ...
myClass.reset(new MyClass());
HappyCoder
HappyCoder
I always try to favor the first method whenever I can. Especially for temporary values on the stack. I use a pointer whenever I need polymorphism, the value is optional, or the value cannot be initialized right away.
My current game project Platform RPG
alvaro
alvaro
I am with HappyCoder: Unless I have a reason not to, I generally use the stack as much as possible. You won't normally run out of stack space because the large memory gobblers are typically standard containers, which use dynamically-allocated memory anyway.

I think using pointers and new for everything is a sign that you should have stayed in Java. smile.png
Khatharr
Khatharr
I've never overflowed the stack outside of bugs and intentional tomfoolery. In fact, I find the default stack length on Win7/VS2015 to be uncomfortably large as it takes too long to overflow when I screw up a recursion or something. (Looking it up, apparently it's only 1MB, lol)

The rule of thumb is to use pointers when you need nullable, reassignable, or late-binding references, use references when you need simple references, and just do everything else by value/on the stack. Raw resource management, including 'new' and 'delete', in the application layer are officially considered an abomination. Use some kind of RAII instead, preferably the native smart pointers and containers for memory.

As you get used to this model you'll find that unique_ptr is nice to have, shared_ptr is a little bit scary (it's not hard to use, but it can sometimes mean that you're doing something silly), and doing everything in-place makes life a lot simpler.

I'm not sure whether you know it already or not, but one thing I see people get stuck on at this level is how to initialize member objects that need constructor arguments. When you see someone using pointers for all their class members it's because they don't know about this. You pass constructor args to member objects with the initializer list on the constructor:
class Foo { //Construction requires an argument!
public: Foo(int val) { value = val; }
private:
  int value;
};

class Bar {
public:
  Bar();
private:
  Foo a; //How can you initialize this? It needs a ctor argument!
  Foo b;
  Foo c;
};

Bar::Bar() : a(42), b(16), c(-1) { //You do it like this. 
  //^ The initializers happen before the function body.
}


You can pass arguments from the owning ctor as well. That is, if Bar::Bar() was Bar::Bar(int arg) then you could say:

Bar::Bar(int arg) : a(arg), b(arg), c(42) { /* stuff */ }
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
noodleBowl
noodleBowl

I think using pointers and new for everything is a sign that you should have stayed in Java. smile.png


This is what I'm trying to avoid. Making all of my C++ very Java esque

What is the general consensus on creating objects on the stack and then having to call an init function?
MyClass myClass;
myClass.Init();
But I kind of feel like this is counter productive to the constructor of the class

The reason I ask all of this is, well, long story short. I'm currently working in Android and struggling with class creation when they use OpenGL calls in their constructors.
As they need a valid OpenGL context in order for them to work/not crash the application. I would like to have my classes created on the stack, but because my constructors have OpenGL calls in them I need a valid OpenGL context created first. This is where I'm not sure how to handle them as things like:

void MainLoop()
{
	bool run = true;
	while(run)
	{
		PollForEvents();
		if(TeminateSignaled)
			run = false;
			
		if(window->OpenGLContext->Inited() == false)
			continue;
		
		//Would like to create objects on the stack. This object uses OpenGL calls in its constructor.
		//Not sure how to do this without either: Keep on creating it (super bad) or having it stuck in local scope
		/*
		
			if(window->OpenGLContext->Inited() == false)
				continue;
				
			if(firstRun)
			{
				SpriteBatcher batcher; //not sure how to use out of local scope
                                firstRun = false;
			}
		
		*/
		
		SpriteBatcher batcher; 
		
		window->OpenGLContext->ClearBufferBits();
		window->OpenGLContext->SwapBuffers();
	}
}
Hope my issue above makes sense, never really had to wait for a window callback (Android triggers onNativeWindowCreated when the window is ready to display / used)in order to tie a OpenGL context to it. Always had something along of the lines of create the window right now and then use it
Zipster
Zipster

The stack requires that the lifetime of your object be known and fixed at compile-time. Indeed, the compiler depends on it to generate the proper stack framing code. The code you have now can't satisfy those conditions because it must wait some indeterminable amount of time before the window is created, thus requiring late-binding and heap allocation.

However let's say you re-arrange your code like this:


void MainLoop()
{
    while(!window->OpenGLContext->Inited()) { /* handle exit */ }

    // Guaranteed OpenGL context is initialize
    SpriteBatcher batcher(window->OpenGLContext);
    
    bool run = true;
    while(run)
    {
        /* main loop stuff */
    }
}

Your code waits until the OpenGL context is initialized before creating the sprite batcher, which can be created on the stack because it's lifetime can be entirely code-driven.

frob
frob




What is the general consensus on creating objects on the stack and then having to call an init function?

In large games it is critical to manage object lifetimes.

You (and your design) need to understand what it means to be in given states.

In particular, a bit of history is important here. I've written about it a few times and an article is probably in order, but I'll go over it again.

Many times you will read articles talking about initialization in multiple steps. It is important to understand what they mean by that. Some things need to happen in multiple steps, other times there are multiple steps that happen behind your back that once were required but now happen automatically.

Back many years ago, "creating an object" was a two step process. The first step was to grab a chunk of memory. You had no knowledge of what was in the chunk of memory, it was just a data block. The second step was to initialize the chunk of memory to a known value. The known value may have been zero, and there are functions like bzero() that set a block to zero, or you could use memset() to force it to specific values, or you could make an object with default known values and use memcpy() to assign those values.

When books and other resources tell you to initialize your objects when you create them, that is what they are talking about. Allocating a chunk of memory and then immediately assigning it a known value.

These days, "creating an object" is normally a single step process. In C++/Java/C#/etc when you create an instance it also calls a constructor and values assume their default state. It is generally good practice to assign default initial values, but sometimes it can make sense for some data elements to be ignored. For example, if you have a buffer and an integer that says the number of elements in the buffer, if you initialize the number to zero you don't need to wipe out the contents of the buffer to any known value since they will be overwritten when an item is added.

Some languages will strictly enforce this rule. Java and C# by default will complain if you attempt to use a value when the compiler knows you have not set the value. C and C++ have enough historical code and enough corner-cases requiring less visible initialization that the languages do not generally complain about the error. They will let you blissfully use an unintialized variable, leaving you unaware of your bug.

Typically you want your default constructor to be instant, or as close to instant as possible. There are many reasons objects get created. You might create an array of objects, you might create temporary objects, you don't want to wait around for those objects to be created. if your default constructor needs to do a bunch of work, like allocating large chunks of memory, or even worse jumping out to the disk or across a network, you can be waiting for an extended time for those temporary objects to be created.

You might want to create additional constructors that do additional work. That additional work can take more time and do more work. The extra effort is acceptable in many cases because the programmer asked for that extra work. These are not temporaries, but actual immediate items.

Let's look at some examples from the C++ standard library.

First, an input file stream fstream. The default constructor, ifstream(), does the minimal amount of work. It constructs an empty stream that is not associated with a file. This takes almost no work and returns instantly. You can create an array of ten thousand file streams created with the default constructor and it will still be nearly instant. There are additional constructors available. A constructor taking a file name, ifstream(filename) will start by building an empty object, then attempt to open a file. The process of reaching out to the disk can take an extended time, maybe disk spindles need to start spinning, maybe those disks are located across a network or even on the other side of the world. The disk may even be associated with an advanced tape system where it takes minutes for a machine to rewind a tape, eject it, locate a new tape, install it to the tape reader, find the file on the tape, and then report that it is open. Attempting to create a large number of files with this parameterized constructor can potentially take an enormous amount of time.

Second, a data container vector. The default constructor, vector(), does almost no work. It may set two or three data members to zero and then return instantly. You can create an array of vectors, that is also nearly instant. There are additional constructors available. A constructor taking an existing data buffer will create copies of every item to be created. If you are creating a copy of other objects that are expensive to create, that constructor can take an extended amount of time.

So getting back to your question:

MyClass myClass;
myClass.Init();

This can make sense in many situations. The first line, MyClass myClass; runs the default constructor. It does no work other than setting the object to a minimally empty state. The second line, myClass.Init(); does additional potentially expensive processing.

When it comes to game object lifetimes, often there is a pattern:

1. Create empty.

2. Load, which figures out what goes in the object and creates a proxy placeholder without doing expensive loading of models and textures and such.

3. Place in world, which usually incurs a cost as the object is hooked into various systems.

4. Stream into world, which replaces the proxy placeholder with exact data.

5. Stream out of world, which frees space and replaces it back with a proxy.

6. Remove from world, which removes the object from various systems.

7. Unload, which places the object back in the free pool

8. Destroy.

There may be more or fewer steps in your specific engine, but that is enough to communicate the idea.

Now objects can move through the system in many ways.

They may have a lifetime where the level gets loaded but the object is never seen because it is far away from the player. Perhaps an object lifetime of create, load, place in world, remove, place, remove, place, remove, unload, destroy.

They may have a lifetime where they are loaded and right at the edge of the player's simulation: create, load, place in world, stream in, stream out, stream in, stream out, stream in, stream out ...

They may have a lifetime as a temporary object: create empty, never get used, destroy.

It is important to understand how object lifetimes work in games.

Juliean
Juliean




I've never overflowed the stack outside of bugs and intentional tomfoolery. In fact, I find the default stack length on Win7/VS2015 to be uncomfortably large as it takes too long to overflow when I screw up a recursion or something. (Looking it up, apparently it's only 1MB, lol)

In hindsight, I wonder why I wrote "you are going to run out very, very soon", formulating it that way really makes no sense. I quess I had situations back in mind where you had large static arrays of thousands of elements:


int array[150000]; // theres half your stack for you

But its right, realistically you won't run out of stack space anytime soon.

NightCreature83
NightCreature83

But what I mean is which one should I be using the most? Does it matter even?

Would it be bad to have all of my custom classes created using the dynamic memory way?

What you should use depends on the situation.

The first example creates the class on the stack. If you leave the scope of the function, it will be destroyed. If you have it as a class member, it will be destroyed with the owning class.

The second example creates the class on the heap. It will be located at a different memory location than in the first example. This has a few effects. First of all as you mentioned it isn't destroyed automatically. This must not be bad - now the class can live way beyond the scope it is created.
I would still recommend never using new where you can avoid it and instead look into the smart pointers - std::unique_ptr at least, which solves the problem with the memory leaks without additional cost, by taking care that "delete" is called automatically when the pointer leaves scope. You'd need c++11 though and look into move semantics, but it definately pays of.


std::unique_ptr<MyClass> myClass;
myClass = std::make_unique<MyClass>();

There are a few more differences between the two things:

- Stack space is limited (a few MB I belive?). If you create everything on the stack, you'll run out of memory very very soon

- If you allocate on stack, you have to know the size upfront. You cannot have dynamic arrays on the stack, you need to new[] there

- You cannot really have polymorphism if you do everything on the stack. Well technically, but... for example, if you have a class with virtual functions that is to inherited by other classes, and you want to store all those objects in a generic container, you have to use a pointer, otherwise it will slice off the derived class part.


class PolymorpicClass
{
    virtual void Foo(void) { exit() };
}

PolymorphicClass class; // you cannot store any derived class like this...
class.Foo(); // always calls "exit()"
PolymorphicClass* pClass; // ... but now you can

- Another kind of important point is performance. If you allocate a variable on the heap, it might lie in a totally different part of memory, meaning that you have an additional memory access (which can be very slow due to how RAM and cache works). Don't get overly paranoid with that though, there is nothing wrong with dynamically allocating memory. Just know that it is exactly the same speedwise.

As a rule of thumb, always allocate on the stack if you can (without making using your code hard than it has to be) and if you aren't in danger of running out of memory. Otherwise, use smart pointers instead of "new".

Stack size is only 1 Megabyte be default in Visual C++, this can offcourse be changed in the settings of the project or when you create a thread. Stack size is not something you can grow dynamically without creating a new thread. The stack size is also important to be aware of when dealing with recursive functions and large parameters, because it limits the recursion depth.

brightening-eyes
brightening-eyes

hello,

in my idea, if you want to use new to allocate dynamic memory, it is best to work with smart pointers, because if you forgot to delete the class, you might ran into memory leeks

but please note that shared_ptr, unique_ptr, weak_ptr and auto_ptr are different

and, i agree with juleian in post #2

but please note that both stack and heep are limited

if you want to get more stack space, you would need to use multi threading

every function needs to be in it's own thread

but as others have said, it depends on situation to use stack or heep

when you can't see well like me, you can't test your applications and you can't read something Github
alvaro
alvaro

if you want to get more stack space, you would need to use multi threading


That's kind of ridiculous. If you want to get more stack space, you are probably doing something wrong, so go fix your code. If you still need more stack space, change your OS configuration (e.g., `ulimit -s ' on Linux).
roblane09
roblane09

Coming back into C++ after a long time, I really like this question. I think I probably would have asked it myself before too long.

I'm curious about the usage of auto pointers or smart pointers with collections from the STL. I think the elements in these containers have to be copy constructible, and I don't think auto pointers are, not sure about smart pointers.

If I populate an STL collection with a large number of objects, I currently use 'new' and 'delete[]', but reading above has me concerned. Any follow up advice on this? Or maybe I should not use STL collections for a large number of objects?

"this feature will ship in version 1.0 for sufficiently large values of 1."
BitMaster
BitMaster
std::auto_ptr are deprecated. They still exist but are scheduled to be completely removed in the forseeable future. Their semantics are extremely weird and they are likely to cause more problems than they solve when not used exactly as intended (and putting them into a container was never intended).

std::shared_ptr is copyable and can be stored in standard library containers without any problems. When stuck on a pre-C++11 compiler boost::shared_ptr can be used to get basically the same functionality.

std::unique_ptr is not copyable but it is movable and can be stored in standard library containers. When stuck on a pre-C++11 compiler there is no really good alternative since they rely on rvalue references. Boost has boost::scoped_ptr but it's not really a good replacement and does not work in containers.

In a lot of cases you will want to avoid using new/delete in favour of std::make_shared/std::make_unique.

Note also that while a lot of people call it 'the STL' that name is in fact incorrect and it refers to the first pre-standard suggestion for a C++ standard library.

If I populate an STL collection with a large number of objects, I currently use 'new' and 'delete[]', but reading above has me concerned.

I suspect that is just a simple typo but this has always been an error: anything allocated with 'new' needs to be deallocated with 'delete'. Anything allocated with 'new []' needs to be deallocated with 'delete []'. Mixing the two in any way is a big error. It usually did not show any symptoms on MSVC but was never healthy and actively failed on other compilers.
ChaosEngine
ChaosEngine

std::unique_ptr is not copyable but it is movable and can be stored in standard library containers. When stuck on a pre-C++11 compiler there is no really good alternative since they rely on rvalue references. Boost has boost::scoped_ptr but it's not really a good replacement and does not work in containers


What about the boost pointer containers?
http://www.boost.org/doc/libs/1_58_0/libs/ptr_container/doc/ptr_container.html
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight
BitMaster
BitMaster
Never had much practical experience with them and I did not want to pull the topic too strongly towards non-C++11. I mainly added Boost's shared_ptr and scoped_ptr as a bridge in case shinylane remembered those from the time he was working with C++11.
It also allowed me to drop C++11 as a search keyword into the post. If you were used to C++ being mostly static and unchanging (as it was pretty much for well over a decade) being pointed into the direction of newer standards is probably helpful for reentry. That said: C++11, C++14, C++17.
roblane09
roblane09

Thanks BitMaster, some great info there and a lot of tweaking in my old ways of C++

I suspect that is just a simple typo but this has always been an error: anything allocated with 'new' needs to be deallocated with 'delete'. Anything allocated with 'new []' needs to be deallocated with 'delete []'. Mixing the two in any way is a big error. It usually did not show any symptoms on MSVC but was never healthy and actively failed on other compilers.

This was indeed a "typo", as what I meant is new and delete without referencing a collection. If I have a container of pointers I have been using traditional new/delete to manage each item in the container. I will definitely be spending some time looking at the referenced standard library objects and functions, seems like I need to just take a look at some newer C++ 101 since I've been away for so long.

"this feature will ship in version 1.0 for sufficiently large values of 1."

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.