MakeShareable usage?

I’m just curious, the documentation states you use this to initialize a TSharedPtr or TSharedRef and thats what I use it for but I’ve been running into issues with deleters and noticed the type its happening on may invoke MakeShareable multiple times for the same instance. So before I go in and change stuff, I was curious if anyone knows if multiple uses of MakeSharable on the same pointer is wrong?

Thank you

MakeShareable creates a new reference controller for the object, so it should only be used exactly one time. After using it once you have to copy the shared ptr to get more of them.

Basically, there are two methods to properly set up a shared ptr:



TSharedPtr<FBlah> SharedBlah = MakeShared<FBlah>();




FBlah* Blah = new FBlah();
...
TSharedPtr<FBlah> SharedBlah = MakeShareable(Blah);


4 Likes

Thanks a lot! The docs should be updated to make this clearer. Also, that is mighty helpful on the MakeShared utility, does that work with move constructors?

I’ll remove the duplicate make shareable call and follow up on if that solves my crash

Update
Removal of additional calls to MakeSharable fixed it! thanks a bunch

1 Like

no. it does not use move constructor. Why would it be moving? New is obviously a new item you get a ptr. The reference controller is pointed to by all of the TSHaredPtrs. There is NO moving going on. A shared reference is also held onto by the reference controller. Move Constructors are for when you initialize a complex type to pass out in a return call. Then instead of giving you a pointer to an item on the stack which is going to be stomped on, It “Moves” to item from the call stack to the result variable that is waiting the return. pointers are not complex.

Well, since the topic is here – anyone have a good quick link that explains TSharedPtr, not just what it is, but why and how to properly use it? My C++ background is pretty out of date (like 30 years out of date) so it’s something that I see but I don’t have a good grasp on.

It’s just Epic’s own version of Cpp shared_ptr.
It’s a lazy/easy way to hold pointers to an object that are not UProperty.

In Unreal Makeshareable(), TSharedRef<>, and TSharedPtr, is used very often for things like Slate UI Widgets because Slate widgets are not UObjects (but still need memory management so we use those instead of UPROPERTY).

https://en.cppreference.com/w/cpp/memory/shared_ptr

To expand on [USER=“434”]BrUnO XaVIeR[/USER]'s explanation, in C++ objects can be allocated on the stack, inline in other objects or on the heap. A stack/inline object is created without a new keyword and is not a pointer:

MyClass myObject = MyClass();

Or simply:

MyClass myObject;

Both cases will call the MyClass’ default constructor. The variable myObject is treated as a value: if you assign it to another MyClass variable, C++ will copy the object. If myObject is a local variable inside a function, method, or if/for/while/etc block, it will be destructed automatically when it goes out of scope. If it’s a property in another class, it will be is destructed when the instance of that class is destructed. This is very simple to work with, but obviously it’s not viable for complex objects and objects that need to be accessed from various places. For that you use heap-allocated objects:

MyClass* myHeapObject = new MyClass();

The new keyword allocates heap memory, constructs a MyClass object there and returns its address (the infamous pointer). The thing is, unlike garbage-collected languages like C# and Java, in C++ it’s the application responsibility to manage memory allocations.

If myHeapObject is a local variable inside a function, the object it points to will not be automatically deleted when the function returns and you will have a memory leak. This is a “dumb” pointer: it stores only the address (a number) to the object memory. Somewhere, your code need to delete the object when it’s no longer needed:

delete myHeapObject;

But what if you shared this pointer with other variables?


MyClass* anotherPointer = myHeapObject;
delete myHeapObject;
delete anotherPointer; // CRASH!

Like said before, pointers are just numbers. The delete keyword just destructs the object at the address you pass to it, it doesn’t touch the value of myHeapObject and much less of anotherPointer. Working with pointers, you need to be mindful of which part of your code has the responsibility of destroying each object.

Smart/shared pointers are tools to create safer pointers to help with this problem by wrapping a pointer inside a value type, using the inline/stack automatic constructor/copy/destructor calls to manage the lifetime of a heap-allocated object. You have a struct that stores a pointer to an object, and a pointer to an integer to count references. In the copy constructor, the object and counter pointers are copied and the reference counter is incremented, and in the destructor the counter is decremented. If the counter reaches zero, the pointer and the counter are deleted. This is a simple implementation, there are other variants (like weak pointers, which store a shared reference but don’t increment the count and can detect if the object has been destroyed elsewhere).

UE4 has it’s own memory management system for the UObject class, which is why you cannot create UObjects with the standard “new” keyword, or allocate them inline inside other classes/structs or in the stack.

4 Likes

Ah, I see. Most of what I do revolves around AActors, and/or letting the Engine deal with lifetimes (such as Components). I think I’ve only dealt with a UObject inside some Plugin code, that used the TSharedPtr stuff, and I wasn’t exactly clear on why it was using shared pointers versus plain pointers exactly.

So, if I’m understanding this correctly, storing a pointer to an AActor in a UPROPERTY() handles refcounts automatically, but doing same with a UObject does not? Or am I still missing a piece there?

The AActor class inherits from UObject class. Anything UObject needs to be stored in UPROPERTY pointers and be created using UE4’s special functions like NewObject() to have lifetime correctly managed by the engine.

Keep in mind that you can only use the UPROPERTY specifier in UCLASS-tagged classes and USTRUCT-tagged structs. If you want to safely store a reference to an UObject in a a plain C++ class/struct you need to use either TStrongObjectPtr<> or TWeakObjectPtr<>, which are special shared pointer structs for UObject-derived stuff. The TWeakObjectPtr doesn’t increment the reference counter, so it’s nice if you need a reference to an object but want to know if it was destroyed elsewhere.

2 Likes