With shared pointers it is very hard to reason at which point things get deleted, from just looking at the code. This can become particularly messy when you communicate them freely across API boundaries, because that allows callers to just store the shared pointers they get from your API instead of e.g. making private copies or not keeping references at all. Especially in large code bases worked on by multiple people, object lifetime will grow beyond anyone’s comprehension, and people writing new code on top of the existing code will have little other choice than just default to copying around the shared pointers everywhere.
If you don’t know what the lifetime of your objects is, it is very difficult to follow RAII best practices, to control memory use (a shared pointer that is held on to where it is not necessary is basically a leak), to avoid high and/or unpredictable allocation/deallocation cost (compared to e.g. arena allocators or just stack copies), etc. On top of that there is a very conceivable risk of introducing cycles (and hence leaks) when overusing shared pointers. And last but not least debugging code that defaults to shared pointers everywhere can be a nightmare.
These are all practical downsides, but to me personally the argument that you have to be explicit about your memory model (allocation, ownership, lifetime) and use the right tools for your design is even more important. Shared pointers are definitely a very useful tool, but only in situations where you truly want to have shared ownership, and not just because they are convenient because they save you from having to think about these things. Over time I’ve found that more often than not, using strictly unique_ptr for ownership, raw pointers and references to avoid copies, and plain value copies where appropriate and not performance critical, leads to code that is much easier to reason about, has less bugs, etc.