Thinking about this only way I could come up so far is by not using an Array Modifier at all, and using hooks instead.
It may have some downsides and limitations like less flexible texturing and not allowing more complex shapes along the length of the curve, and it wont merge the vertex automatically, but the ends wont get deformed this way, and you won't see any seams if the ends match.
Model your pipe exhaust tip regularly as you did, or append it from a library, whichever way suits you best. Can even be a collection Instance
Instead of using an Array Modifier use the bezier curve Bevel option directly, to give it geometry, you may even use another curve object as section for the bevel. Just make sure the exhaust tip and the section match perfectly so there are no visible seams.
Now align your cap object perfectly with the bezier curve end. That means both position and rotation, including curve handles so they line up perfectly. Try scaling the last curve handle to zero in all axis before hooking up.
Use the 3D cursor to snap the cap in place like Select Vertex > Shift + S Cursor to Selected, then exit edit mode select the cap and Shift + S Selection to Cursor.
Now select the Cap then the Curve (by this specific order) enter Edit Mode on the curve, select the last vertex you previously aligned, press Space Bar type Hook and select the operator Hook to Selected Object
This has the positively unexpected advantage that you can now tweak the shape of or pipe directly from object mode without ever entering Edit Mode in your curve.

On a side note, you can use this with an Array Modifier, but it will not always work perfectly. Since the array is based on segments, depending on the particular length of the curve at a given moment, it may not give an exact count of arrayed items so will get gaps at the end, between the last arrayed object and the end cap.
If you plan on applying the modifiers later you can fix it by hand, you can also use very short array segments to minimize visibility at the expense of performance.