-
-
Couldn't load subscription status.
- Fork 23.5k
Make memnew(RefCounted) return Ref, to improve ownership safety
#111965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…rship of it through a reference.
memnew(RefCounted) return Ref, to force callers to take ownership of it through a referencememnew(RefCounted) return Ref, to improve ownership safety
| } break; | ||
| case FileDialog::FileMode::FILE_MODE_SAVE_FILE: { | ||
| ColorPalette *palette = memnew(ColorPalette); | ||
| Ref<ColorPalette> palette = memnew(ColorPalette); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally the preferred style is
| Ref<ColorPalette> palette = memnew(ColorPalette); | |
| Ref<ColorPalette> palette; | |
| palette.instantiate(); |
Some code just wrongly uses RefCounted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is .instantiate() better than memnew?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, before this PR, it was the only way to properly create RefCounted, other than Ref<Something>(memnew(Something)) (which is more verbose and unnecessary). But with this PR, there won't be difference I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense.
In the context of this change, it would also be possible to disallow memnew for RefCounted types (so callers have to use .instatiate() instead). I suppose now would be a good time to figure out which style we prefer going forward.
| } | ||
|
|
||
| MissingResource *missing_resource = nullptr; | ||
| Ref<MissingResource> missing_resource = nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unnecessary.
| Ref<MissingResource> missing_resource = nullptr; | |
| Ref<MissingResource> missing_resource; |
|
There would probably need to be some way to create these without explicit ownership as there are times when we need to not do refcounting for safety, also in some low-level construction I'd say, though usually the cases where we want to avoid refcounting are when we already have a reference and we avoid referencing it again |
|
To me it looks like the problem is that some people used RefCounted wrong. You should either not use it with |
Could you provide an example? Usually the only reason to avoid ref counting is performance, and you can still pass
This is part of the problem, but just using
This PR basically enforces the
Yea, disallowing |
I can't remember where it was currently, but and I think that was related to creating
But that's establishing a different style than what is established |
I meant "new syntax" for RefCounted objects. After this PR you can do
Right, so I guess that would justify this change. |
That is fully code wise possible currently? You don't have to do |
I think using any refcounted object without a primary owner is an error. Passing it around via pointer is fine, but owning one without it is always unsafe, because anyone you pass it to can destroy your object (even by accident).
That's true. I have some problems with
This is already possible in |
|
One potential issue I would see is that this could hide incorrect code, it should be the case that: Ref<T1> t1 = memnew(T2);Fails when Though ultimately we don't want Ref<Texture2D> tex;
...
tex = memnew(ImageTexture(...)); |
That's a good point. This was also previously a problem (when using the |
|
It shouldn't have been a problem as it should fail to compiletime right now, I can't test it right now but it should as So this would be a change in this PR, so I'd say a check for this at compile time should be in this PR I'd test just with: Ref<Texture3D> r = memnew(ImageTexture2D(...));Edit: There isn't really any way I can see that this can be guarded against, not sure how we can resolve that at compile time, as it's just To clarify the problem: This fails (and should fail to make it clear to the user they're doing something wrong): Ref<Texture2D> t = memnew(Texture3D);But this doesn't (as conversion is fully permitted between Ref<Texture3D> t3 = memnew(Texture3D);
Ref<Texture2D> t = t3;The result will be that With this change the first case will not fail at compile time, it will just yield an empty result, making this very hard to diagnose if you, for example, accidentally picked the wrong type when doing something |
|
If we want to continue to allow code like this (from ATS' comment above):
Then I think we need the changes in this PR. Because without this PR, in code like that it's still possible for a class to accidentally delete itself during Maybe some of the push back here can be addressed by changing the |
This PR improves
RefCountedownership semantics for newly created objects, preventing potential crashes and other footguns at build time.Background
When
memnewinitializesRefCountedsubclasses, they start with an (effective) allocation count of 0.This creates two ownership related problems:
RefCounted *initially, some code may increase and later decrease the refcount (e.g. when passing asVariant). This destructs the object, because the refcount is decreased to 0. The actual owner'sRefCounted *will be a dangling pointer. The owner should have storedRef<RefCounted>instead.postinitialize_handler/NOTIFICATION_POSTINITIALIZE,RefCountedobjects still have an allocation count of 0, because the caller ofmemnewdidn't claim a reference yet. If the allocation count is increased and later decreased during this function, the object will unexpectedly destruct (seeRefCountedreleases itself if it is referenced inNOTIFICATION_POSTINITIALIZE#108395).The ideal (only?) way to fix this is to start
RefCountedwith a refcount of 1. Thememnewcaller must take ownership of the refcount after the call. This just means we returnReffrommemnew. Most callers already store the result in aRef, so they are compatible with this change.Overview
Implementing the change was fairly straight-forward; I add and specialize
memnew_result_tsuch that forRefCountedtypes,memnewreturnsRef<T>.The rest of the changes are fixing current ownership problems. Mostly, it just involves changing
T *toRef<T>. It has some inherent risk, but I don't think it's huge.