2

Let's say I have the following function (where mVertexShader is an std::shared_ptr< ID3D11VertexShader > class member, and mRenderData is just POD holding other D3D stuff):

void VertexShader::CreateShader( void )
{
    GVertexShader* raw_VertexShader = mVertexShader.get();

    mRenderData->Device->CreateVertexShader(
        mCompiledShader->GetBufferPointer(),
        mCompiledShader->GetBufferSize(),
        NULL,
        &raw_VertexShader
    );

    delete raw_VertexShader;
    raw_VertexShader = NULL;
} 

since mVertexShader.get() returns the raw pointer, will deleting raw_VertexShader effect the pointer inside mVertexShader.get(), or, while the memory being pointed to is the same, the pointers themselves are completely different?

I'm guessing the latter, hence why reference-to-pointer exists in the first place, but I'd like to be sure that I have the right idea.

Note: I am currently using MSVC++, though it would be nice to hear whether or not there are any significant differences in how MSVC++ and MinGW/GCC implement this.

Note2: I apologize if I wasn't being clear - I'm not referring to semantics of D3D here at all, but more so the semantics of the language of C++ itself in regards to memory management, data transfer, and dynamic memory allocation. In that sense, what I'm trying to understand exactly what happens when a raw pointer is returned from a function.

I.e., assuming mVertexShader is storing a simple raw pointer to an arbitrary VertexShader, when I call its get() method, does it return a copied pointer which points to the same memory address being referenced as the one which sits inside the shared_ptr container, or is it an entirely new pointer altogether that has been copied and returned, with memory being to pointed to at a different address?

zeboidlund
  • 9,731
  • 31
  • 118
  • 180
  • It has nothing to do with the language or the compiler. It's purely up to the design of the classes and the interface they present. If an object owns a sub-object, the deleting the object will delete the sub-object (or at least decrease its reference count and delete it when it reaches zero). You have to read the documentation for the classes you are using. – David Schwartz Jul 29 '12 at 07:30
  • I'm intrigued by your use of `&raw_VertexShader`. Shouldn't you just pass in `raw_VertexShader` to that function? You want to just pass in a pointer-to-VertexShader, don't you? – Aaron McDaid Oct 18 '13 at 16:12
  • The `&raw_VertexShader` is probably the issue. This question is about D3D, even if you don't want it to be. According to the [D3D docs](http://msdn.microsoft.com/en-us/library/windows/desktop/ff476524%28v=vs.85%29.aspx), the fourth parameter is an *out* parameter. I guess this means that D3D will create a new Shader *wherever it wants* and will then use this outparam to return it. If this is the case, all your other questions are not useful (sorry!) – Aaron McDaid Oct 18 '13 at 16:24

2 Answers2

6

This is actually entirely about the semantics of std::shared_ptr. The language is perfectly happy to support methods of returning a pointer to an object, making a copy of an object and returning a pointer to that, or bumping the reference count on an object and returning a pointer to it. You can do all of those things if you want to.

The get method on a shared pointer just returns the raw pointer to the object. No copy is made. You should never delete this pointer for two reasons:

  1. Another object might have a shared pointer to the same object and you'd be deleting the object out from under it. (That's the point of shared pointers.)

  2. When the last shared pointer goes away, the object will be deleted. Deleting an object twice is UB and can cause your code to crash. (That's also the point of shared pointers.)

Let the shared pointers do their job.

Note that if you call get on a shared pointer, you should make sure you keep that shared pointer around for as long as you might use the raw pointer that you got. Otherwise, the object may cease to exist. The logic of shared pointers is that as long as you keep at least one shared pointer around that references an object, it will not be deleted and when the last such shared pointer goes away, the object deletes automatically.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
1

This question is about Direct3D.

(Disclaimer: I've never done any D3D in my life, I'm just trying my best based on reading the docs, but I think I'm right here!)

You're calling a method called CreateVertexShader. Never mind the shared_ptr stuff. You should start your method like this:

GVertexShader* raw_VertexShader = NULL;  // don't initialize
                                         // with get() or anything like that.
mRenderData->Device->CreateVertexShader(
    mCompiledShader->GetBufferPointer(),
    mCompiledShader->GetBufferSize(),
    NULL,
    &raw_VertexShader
);
// The raw_VertexShader will *not* be NULL any more.  It will have the address
// of the shader that was created by CreateVertexShader.

That method (I assume) will create a VertexShader. It will create it wherever in memory it wants, maybe by new VertexShaderImplementation(...) or something. Then, it has to return the pointer to this object to you. Instead of returning it via a return value, it requires you to pass in a pointer-to-pointer-to-ID3D11VertexShader. It's saying "I've made a new object. I have its address, i.e. a pointer-to-ID3D11VertexShader. But in order for me to give you the address, I need you to tell me where to write it down. Give me the address of a pointer-to-ID3D11VertexShader and I'll write it in there. i.e. Give me a pointer-to-pointer-to-ID3D11VertexShader."

This explains the fact that CreateVertexShader takes a **ID3D11VertexShader as its argument. Note the two *s.

So, what does shared_ptr have to do with this?

(Simple answer: nothing! You could just forget about shared_ptr, but that's not modern C++.)

When CreateVertexShader returns, the pointer isn't "owned" by any shared pointer. You have to get the pointer first (i.e. run CreateVertexShader) before you can give ownership of that object to a shared_ptr.

What you're trying to do is to arrange for D3D to directly 'stuff' the pointer directly into your shared_ptr. But that won't work. You need to let CreateVertexShader 'return' its answer first, and then you can put it into the shared_ptr

mVertexShader.reset(raw_VertexShader); // To be called *after* CreateVertexShader

mVertexShader now owns the pointer, and it is the only shared_ptr to do so. If there was any other shader object that was already in there, then it might be deleted now (depending on whether it was the last shared_ptr to it).


(.. to be continued ..)

I'm going to try to directly answer to possible causes of confusion. I'm interested in this sentence (my emphasis):

... does it return a copied pointer which points to the same memory address being referenced as the one which sits inside the shared_ptr container, or is it an entirely new pointer altogether that has been copied and returned?

You're misusing the word 'pointer' a little here. This fragment isn't clear: "entirely new pointer altogether that has been copied".

A pointer is just an address. If you copy the address, you just have another copy of the address. The underlying VertexShader is unchanged. You have one VertexShader. You can have as many copies of the address (i.e. of the pointer) as you wish, it doesn't change anything.

So maybe you meant to ask "Does get() just return the address of the object which is owned by shared_ptr? Or, does it create a copy of the Shader object (which then has a different address) and then return the address of the copied object? The answer to that is the former, it just returns the address. I think you probably suspected that already. It returns the address, but you have to store it somewhere and this storing necessarily involves copying the pointer.

Now, look again at the new code:

GVertexShader* raw_VertexShader = NULL;
mRenderData->Device->CreateVertexShader(
    mCompiledShader->GetBufferPointer(),
    mCompiledShader->GetBufferSize(),
    NULL,
    &raw_VertexShader
);
mVertexShader.reset(raw_VertexShader);

After this has executed, there is still just one VertexShader. It has an address. But that address has been 'written down' in two places. There are two things that know where the shader is. The raw_VertexShader variable still has the address. And another copy of that address is built into the mVertexShader.

Consider those two locations. You cannot arrange for D3D to write the address of its newly created object directly into the mVertexShared. You can get it to write it to raw_VertexShader and then copy it in.

Remember, we're just copying an address around here. An address is just like a scrap of paper, consider the address of the building you live in. We can get D3D to write it one on piece of paper, and then we can copy it to the 'special' shared_ptr piece of paper. Just because we're copying the address, it doesn't mean that the building is being duplicated!


After the call to CreateVertexShared, and before the rest(), you do know the address of the vertex. You also know where you have stored that address. You've stored it in raw_VertexShader. That means you know the address of raw_VertexShader - you know the address of a piece of paper that has the address of a shader written on it. This last address, the address of a piece of piece, is the address that was passed into CreateVector. You say "there is a piece of paper, I want you to create a Vertex Shader and then write its address on it. Which piece of paper, you ask? I'll give you the address of the piece of paper." We cannot find the address of the piece of paper that is inside the shared_ptr. We do know what is written on that piece of paper (it has the address of the VertexShader) but we don't know exactly where that paper is.


Deleting

will deleting raw_VertexShader effect the pointer inside mVertexShader.get(), or, while the memory being pointed to is the same, the pointers themselves are completely different?

In C/C++, you don't really 'delete' pointers. You just delete the object pointed at by the pointer. Imagine a piece of paper with the address of a house. Imagine you have another piece of paper with the same address. Now, if you call delete pointer1, you will just demolish one of the houses. The house will be destroyed, and the land will be made available to others who may want to build a shop there at some point in the future. But both pieces of paper will be unchanged. They will still have the address there, and they will have the same address. But it will just be an invalid address. The shared_ptr will be invalid and your program will crash (or worse!) eventually.

If you want, you could close the function with raw_VertexShader=NULL;. This will simply erase the address from that one piece of paper. That's probably what you want. You want to say to yourself "the shared_ptr owns the object, and it should be the only mechanism I use to access the shader. Therefore, I'm going to be tidy and will erase any spare copies I have of the address."

It should be noted however that raw_VertexShader=NULL; does nothing. This is correct. I'm just using it as an educational tool. It does not affect the piece of paper inside the shared_ptr.

(Sorry for the long answer, I didn't have time for a short one.)

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88