Jump to content
  • Advertisement
Sign in to follow this  
matt77hias

Smart Pointers (shared_ptr and ComPtr)

This topic is 480 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have multiple classes/structs owning and/or using for instance ID3D11Device and ID3D11DeviceContext. Currently I use ComPtr smart pointers for these structures instead of raw pointers in case a class/struct needs to own them and a reference otherwise. Generally, however, my Renderer class is the real owner. If my Renderer is destructed, owning a ID3D11Device or ID3D11DeviceContext is useless since rendering is finished. Is it therefore better practice to always pass references around and use member variable references in case a class needs to 'own'  a ID3D11Device and/or ID3D11DeviceContext? So ComPtr will only be used in my Renderer? It should even be possible to use the more efficient (since no reference counting is required) unique_ptr with a custom destructor making ComPtr useless.

Share this post


Link to post
Share on other sites
Advertisement

Destructor of a pass will release owned object.
Do you mean an explicit reset or just the default behavior when releasing the stack space (i.e. calling destructor on ComPtr)?

it will receive it as a raw pointer

But now you need to provide guarantee of not passing nullptr. Isn't this a bit odd for ID3D11Device and ID3D11DeviceContext, since you "never" expect them to be nullptr.

Share this post


Link to post
Share on other sites
Happy SDE, on 27 Mar 2017 - 9:08 PM, said: Destructor of a pass will release owned object. Do you mean an explicit reset or just the default behavior when releasing the stack space (i.e. calling destructor on ComPtr)?

Renderer owns a Pass.

Renderer is wrapped by std::unique_ptr.

Pass is a Renderer's member (stack allocation, not by new).

When I need release renderer, I call std::unique_ptr::reset().

Pass will automatically destroyed with it's all owned objects (like input layout) via RAII (stack destructor, +ComPtr::Release() for owned objects).

Device lives longer, than renderer (in current implementation, it is a singleton).

 

it will receive it as a raw pointer

But now you need to provide guarantee of not passing nullptr. Isn't this a bit odd for ID3D11Device and ID3D11DeviceContext, since you "never" expect them to be nullptr.

Usually textures are created once (between resizes), context is created once before Renderer.

If something goes wrong, exception will be thrown and there will be no way of calling Render() in this case.

So I have 146% guarantee, that there will be no nullptr at Render() or similar call. :)

It's called an "invariant" in C++.

Edited by Happy SDE

Share this post


Link to post
Share on other sites
input layout

Is your input layout owned by multiple objects? If not, aren't there a lot of ComPtrs with a ref count of 1 and isn't this an overhead compared to unique_ptr with a custom destructor?

 

 

Device lives longer, than renderer (in current implementation, it is a singleton).

If your Device outlives most of your code, why using the same smart pointers. I mean if objects A, B, C all have a ComPtr to the same data, but without A (e.g. your Device), B and C should not exist since no rendering will be performed anymore. The problem with a ComPtr is that your pointed data is not guarenteed to be destructed after destructing A (without using reset) since B and C could still live due to programmers violating the design?

Edited by matt77hias

Share this post


Link to post
Share on other sites

If IL is owned by multiple Passes, there will be AddRef() twice, like standard rules of c++.

This AddRef() will be called only 2 times.

The other option: have SharedObjects object, that will own the IL, and pass raw IL pointer to clients.

 

IF you pass ComPtr by value in each Render() call, this is a problem.

But if you only store it as a member - no overhead.

shared_ptr is the same as AddRef(): ++ operator is atomic.

 

Situation when more than 1 object owns the same object is quite rare (in my code).

Usually the construction is multi-level:

Device -> Renderer -> Pass.

For GameRenderer, I know what passes are there, and their order.

For SplashScreenRenderer, passes are different, but they are known.

It is static on compilation time.

Customization is available in construction time (in constructors), for example: MSAA/no MSAA version of a shader.

The other customization option have 2 version of a renderer with the same interface:

Renderer::std::unique_ptr<ISsao> m_ssao;

And have 2 implementation of it: NoSsaoPass and SsaoPass.

NoSsaoPass will do nothing.

 

But after all is created, there is no need to destroy any pass/any of it subobject.

There is only need to update buffers/call Render() in some (strict) order.

This is a job of a particular renderer (like SplashScreenRenderer or GameRenderer).

 

When a game is finished, entire renderer is destroyed (with all Passes, and it's subobjects), and new renderer (SpashScreenRenderer will be created).

The device and context will be still alive.

Edited by Happy SDE

Share this post


Link to post
Share on other sites

Option1:

class Renderer{
   Renderer(): m_passA{m_shared.m_sharedIL.Get()}, m_passB{m_shared.m_sharedIL.Get()}{}
   
   SharedObjects m_shared; //Should be before m_passA and m_passB
   PassA         m_passA;
   PassB         m_passB;
}
struct SharedObjects
{
    SharedObjects() {build m_sharedIL, if something went wrong, throw exception}
    ComPtr<IL> m_sharedIL;
}
class PassA
{
    PassA(IL* il) : m_il{il}{}
    IL* m_il;
}
class PassB
{
    PassB(IL* il) : m_il{il}{}
    IL* m_il;
}

Option2:

No SharedObjects class, but PassA and PassB will store ComPtr to the same object;

Edited by Happy SDE

Share this post


Link to post
Share on other sites

Option1:

class Renderer{
   Renderer(): m_a{m_shared.m_sharedIL}, m_b{m_shared.m_sharedIL}{}
   
   SharedObjects m_shared; //Should be before m_a and m_b
   PassA m_a;
   PassB m_b;
}
struct SharedObjects
{
    ComPtr<IL> m_sharedIL;
}
class PassA
{
    PassA(IL* il) : m_il{il}{}
    IL* m_il;
}
class PassB
{
    PassB(IL* il) : m_il{il}{}
    IL* m_il;
}

Option2:

No SharedObjects class, but PassA and PassB will store ComPtr to the same object;

Option 3 : if you use IL& instead of IL* in PassA and PassB, you could use unique_ptr<IL> with a custom deleter?

Share this post


Link to post
Share on other sites

Option 3 : if you use IL& instead of IL* in PassA and PassB, you could use unique_ptr with a custom deleter?

How many D3D objects do you have?

(how much bytes are you going to save?)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!