Smart Pointers (shared_ptr and ComPtr)

Started by
28 comments, last by Happy SDE 7 years ago

1. Real owner - is SharedObjects object. 2. It will outlive PassA && PassB => no need them to be owners. 3. For usage, D3D requires from you IL raw pointer, not ComPtr => You will need to call ComPtr<>::Get() in Render() or just store it as raw pointer.

Ok thank you, then am going to change all my ComPtr usage, since only one object really has to own them whereas the other objects could hold on to a raw pointer. Even though it does not represent ownership anymore, it still works for move semantics (as opposed to references).

🧙

Advertisement

If you pass std::string by value to a function, there will be "new" + copy. Move will not work here.

Thats not true at all.


void test(std::string value)
{
}

std::string strTest = L"HelloWorld";
test(std::move(strTest)); // calls move-ctor of "value"

This will only involve the move-constructor on the call to test, no copy. Obviously if you don't call std::move it will invoke a copy, and using std::string&& might avoid a move-ctor call in unoptimized builds, but other than that, passing by value is perfectly fine with move-semantics, and is probably prefered for situations where you don't know if the function is called with a temporary or an object to copy. Onyl real downside is that debugging gets slightly more complicated, as now you have to step through the std::string move-ctor before you can enter the function body.

Thats not true at all.

With one small caveat: it will destroy initial strTest:


void Test(std::string value)
{
    std::cout << "InTest: " << value << '\n';
}

void __cdecl main()
{
    std::string strTest = "HelloWorld";
    Test(std::move(strTest)); // calls move-ctor of "value"
    std::cout << "After Test: " << strTest << '\n';
}

InTest: HelloWorld
After Test:
Press any key to continue . . .

Yeah, it will destroy strTest, but after it has been moved, so it will boil down to "delete nullptr" (and also invoke an additional move-ctor compared to passing by std::string&&)- but how is this different to passing by && and moving? Or did you try to make an entirely different point?

Plus, I haven't tested it, but I just hope compilers are smart enough to do something about this minor overhead and compile the underlying assembly to be on par with passing by std::string.

We've discussed in this topic "const T&", T*, and "T" mostly related to ComPtr.

BTW try to pass ComPtr<ID3D11DeviceContext> as && (instead of const T& or T) :)

And tell me, how much frames will you render :wink:

Upd: If you pass it as T, it will call AddRef()/Release() on each call

Well as has been my point, if you can make use of move semantics, "T" is just fine. Otherwise certainly eigther variant of "T&" or "T*" (some common guidelines suggest never to pass a T&, but always make write-to objects passed by pointer, though I wouldn't necessary agree).

BTW try to pass ComPtr as && (instead of const T& or T) :)

Uh, I'm actually really dead-tired right now so excuse me if I don't get stuff anymore, but how does CComPtr<T>&& differ to const T& (or const CComPtr<T>&) in terms of performance, specifically so that it would notibly affect the framerate? You'd have one potential, additional dereference, which you should need anyways when you access an underlying CComPtr the first time and which should thereafter be resolved by cache-hits.

I mean, I agree that you should always just pass "T" in any variant where you don't require to store an object by smart-pointer, but your other points seriously put me off :>

BTW try to pass ComPtr as && (instead of const T& or T) :)

Uh, I'm actually really dead-tired right now so excuse me if I don't get stuff anymore, but how does CComPtr<T>&& differ to const T& (or const CComPtr<T>&) in terms of performance, specifically so that it would notibly affect the framerate? You'd have one potential, additional dereference, which you should need anyways when you access an underlying CComPtr the first time and which should thereafter be resolved by cache-hits.

I mean, I agree that you should always just pass "T" in any variant where you don't require to store an object by smart-pointer, but your other points seriously put me off :>

It differs in a way, that when you call Render(ComPtr<Context>) with move() like this:


struct PassA {
    void Render(ComPtr<Context> ctx);
}
class Renderer {
    ComPtr<Context> m_context;
}
Renderer::Render(){
    m_pass.Render(std::move(m_context));
    //m_context now contains nullptr
} 

After Render() call, m_context will be empty (it will be swapped with empty ComPtr).

And you will have AV next time.

If m_context is a raw ID3D11DeviceContext* (with assumption it is owned by device, which is outlive the renderer):

1. no move is needed

2. and no deref /Get() is needed.

3. And is always not a nullptr (by design)

In other words: use move() only for objects, that are no longer needed.

And make it explicit.

Making each function having T param instead of const T& is not good either: all clients will need to:

1. Have additional copy construction by default (via your interface).

2. Make a copy of their data and pass the copy to your function if they are interesting to keep their data.

2. and no deref /Get() is needed.

I suppose Get() would not be an issue, since ComPtr is a template which can thus definitely be actually inlined by a compiler.

Making each function having T param instead of const T& is not good either: all clients will need to: 1. Have additional copy construction by default (via your interface). 2. Make a copy of their data and pass the copy to your function if they are interesting to keep their data.

Couldn't you just provide two variants for a method like copy/move constructor pair and copy/move assignment operator pair?


3. And is always not a nullptr (by design)

Still, I see two drawbacks: 1) one could violate the design and pass nullptr (but I guess this will trigger something) 2) one could delete or release the pointer. So somehow you should want to pass a "view" (database terminology) to your Passes. But wrapping all D3D11 functionality seems so cumbersome.

🧙

After Render() call, m_context will be empty (it will be swapped with empty ComPtr). And you will have AV next time.

Well, first of all now we were talking about CComPtr<>&& and not CComPtr<>. If you pass by T&&, technically there is no difference to passing by &, since both can or cannot be moved inside the function (though && obviously documents that you plan on moving the object).

Sure though, if you don't attempt on moving the object you should not pass by T&& or T, agreed. I just commented because it seemed like there was some confusion about move semantics.

Still, I see two drawbacks: 1) one could violate the design and pass nullptr (but I guess this will trigger something) 2) one could delete or release the pointer. So somehow you should want to pass a "view" (database terminology) to your Passes. But wrapping all D3D11 functionality seems so cumbersome.

Let me describe my view point:
In MY engine, only I can violate the design and pass nullptr/release COM object.

There are 3 layers of making checks:
1. Compile time checks.
2. Run time invariant
3. Defensive programming.

Compile time checks: the best way to go: no user overhead, checks on build, (simplified version of unit testing for your API/design).
There are tools:
a. /W4 for projects + “treat warning as error”
b. Static code analysis (Carmack talked about it (from ~55 minute)

c. External lib: Sutter presented it on CppCon:

and

I have A and B. C is scheduled to be added this summer.

Runtime invariant: throw in constructor, if you can’t construct an object.
After this point you have fully constructed objects, that have their contracts.
From my side, I expect them work correctly. Benefit: no checks required in rendering time.

Defensive programming: you are not sure about correctness of your code/data, and you need place a lot of checks.
I decided that this is a bad thing: code bloat, performance degradation, more code = more bugs.

On top of that there is MODERN C++. This is a toolbox.
You can peek subsets of:
1. Good practices to follow, and place them to your code/design standard/pattern standard (like owner/user resource ownership)
2. Bad practices not to follow.
There is no good mapping (yet?) between your MS Word design document and code.
So, this guarantee you should give.

(Especially) If you expect that some other coder will touch your code, you’d better to stay away from bad practices like: always pass by value with expectation, that someone will push there r-value reference. Const reference was invented for this job. And this is standard, well-known practice.

Just use right (modern subset of C++) tools in right place. Use good practices, don’t use practices from tutorials where C/C# programmers wrote their samples in C++ (as they though it was C++ :D .

If it is too difficult, you can use other languages, where some of the practices are absent (and you have less potential errors) like C# or whatever.

But the power of C++ is resource management.

You can shoot yourself in a foot, but I don’t want to do it for myself =)

The other thing to remember: what skills will have “potential” co-writer of your code.
If he will be junior, or C# programmer, you will have to train him to your design decisions/ guidelines, or lower your design expectations. IMHO it is difficult to mix it.

Happy coding! :wink:

This topic is closed to new replies.

Advertisement