The answer to 'good practice' is difficult. I would say, in general not good practice, but for some specific use cases very good practice. It all depends how well you can trust the client on the lifetime of the provided memory. In general: No trust. In special cases: OK.
There is one rather important thing to consider performance wise:
Do you plan to use implicit sharing (with copy on write and reference counting) of your allocated variants of your strings? Or do you plan to use value semantics (always copy, never reference)?
In multiprocessor and multithreaded environments value semantics are the preferred way for strings. The performance gain by using implicit sharing is destroyed by the necessary locking in multithreaded environments.
Note that your suggestion may still makes sense even when multithreaded: You can perfectly use copy-on-write when going from your external memory to the allocated variant (no locking necessary), and value semantics from then on (also no locking necessary). This would be best.
I would imagine your variant works well in very specific use cases where for example you memory-mapped some file with lots of strings and you do not want to store copies of these small strings and fragment memory.
However, in the general case I would not worry and just use std::string
.