Historically, there is a “canonical form” for the assignment operator that looks something like the above.
This style of code pre-dates C++ exception handling and is not safe. Either of the “new” expressions could reasonably throw leaving the p1 and p2 pointers dangling. In fact, unless we assume that deleting the old parts won’t throw an exception we can’t even produce a “destructible” whole. (Presuming that the destructor attempts to delete the parts.)
[FWIW This assumption is a reasonable requirement - we’ll come back to it in the next section.]
An obvious reaction to the problems caused by an exception being propagated is to catch the exception and do some clean-up before throwing it again...