I think the phrasing of this answer is misleading for people who aren’t already familiar with these types of systems (reference counting, GC, preprocessor macros, etc).
Example:
“By declaring UPROPERTY() that essentially lets the garbage collection system know about that reference.” <= Correct
“If you declare just a UObject *, that is a Hard reference, which means that the referenced object will not be freed as long as their is a chain of references from the Root to that object.” <= Correct, only given the previous statement, but the phrasing of the sentence implied to me that UPROPERTY was not needed for a hard reference (even though I knew that it must be).
Something like:
If you declare a UObject * it is a raw C++ reference that the UE garbage collector will not know about (aka an unsafe pointer). The UObject may be collected, the pointer will not be NULL, and there will be no way to determine if the pointer is safe to use (hence unsafe pointer).
Marking the UObject * (unsafe pointer) with the UPROPERTY macro will automatically inform the GC of the reference - implicitly making it a hard reference (strong pointer). A UObject can not be freed until no hard references exist from the Root to that object.
A TWeakObjectPtr (weak pointer) allows referencing UObjects without preventing garbage collection. Remember that the referenced object may be collected while you aren’t paying attention, so check IsValid() or Get() != nullptr or you will inadvertently free all the memory as your game crashes to desktop.