Scope of local variables and their destructors

Started by
24 comments, last by noodleBowl 6 years, 6 months ago

Its been a while since I have done C++ and I was wondering if someone could help me understand local variables and what happens they go out of scope

So I have this method for creating a vertex shader and it returns a custom class called VertexShader. But before it returns the VertexShader it places it into a std::unsorted_map called vertexShaders


//Declared at the ShaderModule class level. Map to store VertexShaders in
std::unordered_map<LPCSTR, VertexShader> vertexShaders;

//Method to create a vertex shader. Returns a VertexShader
VertexShader ShaderModule::createVertexShader(LPCWSTR fileName, LPCSTR id, LPCSTR entryPoint, LPCSTR targetProfile)
{
	ID3DBlob *shaderCode = compileShaderFromFile(fileName, entryPoint, targetProfile);

	D3D11_INPUT_ELEMENT_DESC inputElementDescription[] = {
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};

	VertexShader vertexShader(id); //Create a new VertexShader
	device->CreateVertexShader(shaderCode->GetBufferPointer(), shaderCode->GetBufferSize(), NULL, &vertexShader.shader);
	device->CreateInputLayout(inputElementDescription, 2, shaderCode->GetBufferPointer(), shaderCode->GetBufferSize(), &vertexShader.inputLayout);
	shaderCode->Release();

	vertexShaders[id] = vertexShader; //Place into the VertexShader map. Places copy?
	return vertexShader;
}

Now at the end of the method, right as the return is done, I see that the destructor for my variable vertexShader gets called. And this might sound ridiculous, but I really dont understand why?

Shouldn't it live on in memory since it was placed into the vertexShaders map and only when the vertexShaders map is destroyed then the deconstructor for my VertexShader is called?  By doing vertexShaders[id] = vertexShader am I really just making a copy and then eventually another deconstructor for the VertexShader placed into the map will be called?

 

Also when using the method the destructor is called twice? VertexShader being returned from the method now also getting destroyed?


//Declaration of the vertex shader. Done at the class level
VertexShader vShader;

//Method that uses the createVertexShader
void GraphicsSystem::initialize(HWND windowHandle)
{
	/* Other init code*/

	vShader = shaderModule.createVertexShader(L"default.shader", "VertexShader", "VShader", "vs_4_0");
}

 

Advertisement

Placing your variable into the std map dose not give it a scope as it does in C# for example. C++ dosent know if an object is in scope or out of it, this would be the task for a memory/garbadge collector system you would have to write by yourself. This line


VertexShader vertexShader(id); //Create a new VertexShader

creates a local variable of type VertexShader on the stack that lives as long as you remain inside stack scope. And when calling


return vertexShader;

it returns a new copy of your stack scoped variable to the recipient that called your setup function and after that cleans up the stack by removing your local instance from memory. The same is true for your unordered map since it isnt a pointer, the insert function of the std map creates a copy of your object inside the memory std map manages.

The only chance you have here is to use either some kind of ref pointer that takes responsability for keeping your instance alive as long as there are references to it alive or use a raw pointer so you need to take responsability to delete it after you were done so memory could be released properly (destructor wont be called since delete). Otherwise you would have two different copies and in the most anyoing case (as yours occuring) it will happen that your class gest destructed and data you previously attached gets lost

2 hours ago, Shaarigan said:

it returns a new copy of your stack scoped variable to the recipient that called your setup function and after that cleans up the stack by removing your local instance from memory.

The copy is not guaranteed due to C++'s Return Value Optimization.

2 hours ago, Shaarigan said:

class gest destructed

The object or class instance gets destructed, not the class itself.

3 hours ago, noodleBowl said:

it returns a custom class called VertexShader

Idd.: An object or class instance is returned, not the class itself.

3 hours ago, noodleBowl said:

By doing vertexShaders[id] = vertexShader am I really just making a copy and then eventually another deconstructor for the VertexShader placed into the map will be called?

Yes, the copy constructor is used resulting in a copy of vertexShader.

🧙

4 hours ago, noodleBowl said:

Also when using the method the destructor is called twice?

Take a look at the Return Value Optimization example on Wikipedia. It is possible that the copy assignment operator is invoked twice (which also implies that the destructor is invoked twice). Although, I really doubt that any C++11/14 compiler would use a copy assignment in the presence of move semantics?

🧙

2 hours ago, Shaarigan said:

it returns a new copy of your stack scoped variable to the recipient that called your setup function and after that cleans up the stack by removing your local instance from memory. The same is true for your unordered map since it isnt a pointer, the insert function of the std map creates a copy of your object inside the memory std map manages.

The only chance you have here is to use either some kind of ref pointer that takes responsability for keeping your instance alive as long as there are references to it alive or use a raw pointer so you need to take responsability to delete it after you were done so memory could be released properly (destructor wont be called since delete). Otherwise you would have two different copies and in the most anyoing case (as yours occuring) it will happen that your class gest destructed and data you previously attached gets lost

Thats not the case in modern C++ (>11) anymore. With a proper move-constructor/assignment operator, instead of creating a copy it will move the data of the local "vertexShader" variable into the target on the callers side. If you define VertexShader as non-copyable (deleted copy- ctor/assignment-operator) then you can make sure this is happening all the time, even before the recent http://en.cppreference.com/w/cpp/language/copy_elision. (which is different to RVO; but probably won't happen if you assign to a global like in the example-code)

However, one place this code certainly does create a copy is this:

4 hours ago, noodleBowl said:

vertexShaders[id] = vertexShader;

So the code as shown still is flawed in this way. But, you could easily do:


VertexShader& ShaderModule::createVertexShader(LPCWSTR fileName, LPCSTR id, LPCSTR entryPoint, LPCSTR targetProfile) //  returns reference
{
	ID3DBlob *shaderCode = compileShaderFromFile(fileName, entryPoint, targetProfile);

	D3D11_INPUT_ELEMENT_DESC inputElementDescription[] = {
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};
  
    VertexShader& vertexShader = vertexShaders.emplace(fileName, id).first->second; // construct the shader inside the map => might need to adjust the call at the end, not sure about the iterator-syntax

	device->CreateVertexShader(shaderCode->GetBufferPointer(), shaderCode->GetBufferSize(), NULL, &vertexShader.shader);
	device->CreateInputLayout(inputElementDescription, 2, shaderCode->GetBufferPointer(), shaderCode->GetBufferSize(), &vertexShader.inputLayout);
	shaderCode->Release();

	return vertexShader;
}

 

3 minutes ago, Juliean said:

std::unique_ptr &

A reference as data element in the collection?

🧙

Just now, matt77hias said:

A reference as data element in the collection?

Eh, forget that part, I had a brainfart and assumed std::unique_ptr would reallocate elements as it grows. See my edit, thats how I would do it.

Still I don't quite get your question. What i indendet was:


std::unordered_map<LPCSTR, std::unique_ptr<VertexShader>> vertexShaders;

But since (I hope I've got it right now that) its safe to keep a reference to an element in an unordered_map, thats unnecessary.

2 minutes ago, Juliean said:

std::unordered_map<LPCSTR, std::unique_ptr<VertexShader>> vertexShaders;

Ah ok, I thought you meant something like this:


std::unordered_map< LPCSTR, std::unique_ptr< VertexShader > & > vertexShaders;

 

🧙

As a side note:

You can use a create a resource manager that constructs instances of a subclass of VertexShader (which should have a virtual destructor) by using variadic templates to pass the constructor arguments to the base class VertexShader, and that stores weak pointers to them. That way the resource manager is kind of notified on deletion.

Take a look at: SharedresourcePool in the DirectXTK.

🧙


 

Let me see if I have this right


// VertexShader class
class VertexShader 
{
  public:
  LPCSTR id;
  VertexShader() {
  }
  VertexShader(LPCSTR id) {
    this->id = id;
  }
  ~VertexShader() {
  }
};

//Test method
VertexShader ShaderModule::test() {
  //Create a VertexShader instance in local scope called shaderLocal
  VertexShader shaderLocal("localScope");
  
  return shaderLocal; //Copy shaderLocal to send back with the return (Send_Back_Copy). shaderLocal's destructor is called
}

//Main method
void main() {
  //Call the test method
  VetexShader shade = shaderModule.test(); //Send_Back_Copy from return is copied and saved into shade. Sent_Back_Copy's destructor is called
}
//shade's destructor is called after main exits

Is that is what is happening? Or do I have this totally wrong?

This topic is closed to new replies.

Advertisement