PERF: Index._shallow_copy shares _cache with copies of self
#32640 copied the cache when copying indexes. Indexes are immutable, so some refactoring allows us to __share__ the cache instead. This means potentially fewer expensive index ops.

```python
>>> idx = pd.CategoricalIndex(np.arange(100_000))
>>> copied = idx.copy()
>>> copied._cache
{}
>>> idx.get_loc(99_999)
>>> copied._cache
{'_engine': <pandas._libs.index.Int32Engine at 0x1fd3a6934c8>}  # don't need to recreate _engine on copied
```

Performance example:
```python
>>> idx = pd.CategoricalIndex(np.arange(100_000))
>>> %timeit idx._shallow_copy().get_loc(99_999)
4.09 ms ± 65 µs per loop  # master
7.71 µs ± 355 ns per loop  # this PR
```

The issue @jreback raised means sharing ids is a bit more complicated than thought.

I've repurposed this PR to make indexes share caches, as that was planned in a follow-up, and the code for that is not that different. Sharing caches gives some performance improvements also (in different circumstances than sharing _ids, but still nice). ping. looks good @topper-123 
@jbrockmendel if you can just give another set of eyes here and merge when ready. thanks @topper-123 