Instead of handling AddDynamic and RemoveDynamic in the constructor/destructor, have you tried handling them in the BeginPlay() and EndPlay() functions? If you want to be safe and ensure that the delegates are bound as soon as possible, you could call Super::BeginPlay() and Super::EndPlay()after the delegates are added/removed.
To answer the title question, I do not believe so. I think all delegates must manually be removed. Ideally EndPlay() seems like the best place to handle it, from my experience (That said, UE4-built-in delegates might handle their cleanup differently).
For implementation details on delegates, see Engine\Source\Runtime\Core\Public\UObject\ScriptDelegates.h.
Multicast dynamic delegates store an array of script delegates. The concern with not calling RemoveDynamic is that we’ll be adding things to this array and never removing them.
If we look at the definitions for AddDynamic, AddUniqueDynamic, and RemoveDynamic, we see that we call the function CompactInvocationList() before each one, which is defined as follows:
For implementation details on delegates, see Engine\Source\Runtime\Core\Public\UObject\ScriptDelegates.h.
Multicast dynamic delegates store an array of script delegates. The concern with not calling RemoveDynamic is that we’ll be adding things to this array and never removing them.
If we look at the definitions for AddDynamic, AddUniqueDynamic, and RemoveDynamic, we see that we call the function CompactInvocationList() before each one, which is defined as follows: