Why a Cinder App is not a shared_ptr


#1

Hello,

As an experiment I wanted to check if I can derive a Cinder App class from std::enable_shared_from_this so I can pass a shared_ptr of my main app to its members as a way of communication/messaging between an App and it’s members(also between public members themselves) so something like this can be called from a member:

this->mParentApp->mOtherMember->doThis();

When trying it and calling shared_from_this() in my App’s setup method, I got the bad_weak_ptr exception. After looking it up I realized that the exception is because “Prior to calling shared_from_this on an object t, there must be a std::shared_ptr that owns t”, That made sense. When I looked at Cinder’s source, I saw that in headers such as AppMac or AppMsw, when calling the main<T>() method, a raw pointer of AppT is created and casted as the desired App, not a shared pointer.

I know I could implement the same system with raw pointers and I was experimenting anyways but the question bugged me to be honest, If Cinder has fully embraced C++11 and is using its features like shared pointers in so many places, why not the main App? Is there any specific reason behind this design? Hypothetically if a Cinder App needed to be a part of a bigger ecosystem, it being a shared_ptr might help.

I hope I’m making sense :slight_smile:
Thanks


#2

The thing about idioms is that they are just that. There’s no one true way, as it were. I like to refer to this page to at least get ideas for design idioms https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md

Please bear in mind I am not a Cinder architect, so I am speaking out of turn, but do feel like throwing in two cents. In the spirit of the isocpp document, I might suggest that an idiomatic way of handling a singleton object like a CinderApp would be to pass App references, not pointers, as ownership against a reference is perfectly clear. At the outermost scope one might have a unique_ptr or a stack object scoped to main to enforce that no one else ever takes ownership of the app.


#3

Hi,

I am not the architect either, so there’s your disclaimer. But theoretically, a shared_ptr doesn’t make sense, because that would suggest that the App instance can be destroyed or copied. This is not the case. There can only be one instance. And while not technically a singleton, it acts as one. Therefor, a raw pointer, at least to me, seems like the correct way to give access to the instance.

-Paul


#4

Thanks guys for your feedbacks. Rethinking the issue with what you said in mind, now I also believe that making a shared_ptr out of an App can be potentially dangerous since it tells the owner that in can be shared which is wrong.

Also the hypothetical case I proposed won’t happen, because if someone needs to use an App in a bigger system, then the main<T>() method won’t be used anyways and the user will have to handle the creation himself in which case the management of the App’s lifetime is on his shoulders.

Having said that, the idea of having it living as a unique_ptr is still viable IMO and might fit the idiom even better. As you both said though, these are architecture level discussions and agreeing that “there’s no one true way”, I totally honor the architect’s preference. I’m massively enjoying Cinder and am learning so much from its source code.


#5

But then you’d need to pass the unique_ptr by reference, or even dereference it with its unique_ptr::get() function in order to pass it from ci::app::App::get() (a unique_ptr can’t be copied) and then people will start asking why it is a unique_ptr in the first place.

By the way: thanks for asking your question. It’s good to have discussions like these, because it forces us to think about (and maybe even defend) Cinder’s design choices.


#6

When I hold something in a unique_ptr, I always pass a reference, or a const reference if appropriate. No ambiguity about ownership. Passing the pointer from get() is a can of worms because you can say you have a convention that raw pointers are not owned, but the bigger the codebase gets, the harder it’ll be to enforce. Another advantage of a reference over a pointer is that references can’t be re-assigned. References are the only case where the language helps you out a bit, although you can still make a mess by taking a pointer to the reference.

void DoSomethingWithApp(const App&);

int main() {
std::unique_ptr singleton(new App());
DoSomethingWIthApp(*singleton.get());
}

Along those lines, a singleton with private constructors and destructors, constructed by a factory and returned as a unique_ptr with a deleter is often a dandy thing, especially if the class itself is marked nomove and noncopyable.