Merge branch 'master' into sh_merge_master
diff --git a/.codespell-ignore-lines b/.codespell-ignore-lines
index 2a01d63..e8cbf31 100644
--- a/.codespell-ignore-lines
+++ b/.codespell-ignore-lines
@@ -12,6 +12,17 @@
     template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra>
     class_ &def(const detail::op_<id, ot, L, R> &op, const Extra &...extra) {
     class_ &def_cast(const detail::op_<id, ot, L, R> &op, const Extra &...extra) {
+    int valu;
+    explicit movable_int(int v) : valu{v} {}
+    movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; }
+    explicit indestructible_int(int v) : valu{v} {}
+    REQUIRE(hld.as_raw_ptr_unowned<zombie>()->valu == 19);
+        REQUIRE(othr.valu == 19);
+        REQUIRE(orig.valu == 91);
+        (m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"),
+atyp_valu        rtrn_valu() { atyp_valu obj{"Valu"}; return obj; }
+    assert m.atyp_valu().get_mtxt() == "Valu"
+// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const,
 @pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
 struct IntStruct {
     explicit IntStruct(int v) : value(v){};
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 33e281d..e3f2370 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,6 +7,7 @@
     branches:
       - master
       - stable
+      - smart_holder
       - v*
 
 permissions: read-all
@@ -87,6 +88,34 @@
           # Extra ubuntu latest job
           - runs-on: ubuntu-latest
             python: '3.11'
+          # Exercise PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
+          # with recent (or ideally latest) released Python version.
+          - runs-on: ubuntu-latest
+            python: '3.12'
+            args: >
+              -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT"
+          - runs-on: macos-13
+            python: '3.12'
+            args: >
+              -DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT"
+          - runs-on: windows-2022
+            python: '3.12'
+            args: >
+              -DCMAKE_CXX_FLAGS="/DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT /GR /EHsc"
+          # Exercise PYBIND11_SMART_HOLDER_DISABLE
+          # with recent (or ideally latest) released Python version.
+          - runs-on: ubuntu-latest
+            python: '3.12'
+            args: >
+              -DCMAKE_CXX_FLAGS="-DPYBIND11_SMART_HOLDER_DISABLE"
+          - runs-on: macos-13
+            python: '3.12'
+            args: >
+              -DCMAKE_CXX_FLAGS="-DPYBIND11_SMART_HOLDER_DISABLE"
+          - runs-on: windows-2022
+            python: '3.12'
+            args: >
+              -DCMAKE_CXX_FLAGS="/DPYBIND11_SMART_HOLDER_DISABLE /GR /EHsc"
         exclude:
           # The setup-python action currently doesn't have graalpy for windows
           - python: 'graalpy-24.1'
diff --git a/.github/workflows/configure.yml b/.github/workflows/configure.yml
index 2031ec8..8e89e6f 100644
--- a/.github/workflows/configure.yml
+++ b/.github/workflows/configure.yml
@@ -7,6 +7,7 @@
     branches:
       - master
       - stable
+      - smart_holder
       - v*
 
 permissions:
diff --git a/.github/workflows/emscripten.yaml b/.github/workflows/emscripten.yaml
index 5eac089..c7fd73c 100644
--- a/.github/workflows/emscripten.yaml
+++ b/.github/workflows/emscripten.yaml
@@ -6,6 +6,7 @@
     branches:
     - master
     - stable
+    - smart_holder
     - v*
 
 concurrency:
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
index e50dc0b..6bb2217 100644
--- a/.github/workflows/format.yml
+++ b/.github/workflows/format.yml
@@ -10,6 +10,7 @@
     branches:
     - master
     - stable
+    - smart_holder
     - "v*"
 
 permissions:
diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml
index c10087e..a32e7fe 100644
--- a/.github/workflows/pip.yml
+++ b/.github/workflows/pip.yml
@@ -7,6 +7,7 @@
     branches:
     - master
     - stable
+    - smart_holder
     - v*
   release:
     types:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1abc480..fbecbc0 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -76,6 +76,7 @@
   - id: mixed-line-ending
   - id: requirements-txt-fixer
   - id: trailing-whitespace
+    exclude: \.patch?$
 
 # Also code format the docs
 - repo: https://github.com/adamchainz/blacken-docs
@@ -90,6 +91,7 @@
   rev: "v1.5.5"
   hooks:
   - id: remove-tabs
+    exclude: (^docs/.*|\.patch)?$
 
 # Avoid directional quotes
 - repo: https://github.com/sirosen/texthooks
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9357f31..d8323c1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -131,10 +131,13 @@
     include/pybind11/detail/common.h
     include/pybind11/detail/cpp_conduit.h
     include/pybind11/detail/descr.h
+    include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
     include/pybind11/detail/init.h
     include/pybind11/detail/internals.h
+    include/pybind11/detail/struct_smart_holder.h
     include/pybind11/detail/type_caster_base.h
     include/pybind11/detail/typeid.h
+    include/pybind11/detail/using_smart_holder.h
     include/pybind11/detail/value_and_holder.h
     include/pybind11/detail/exception_translation.h
     include/pybind11/attr.h
@@ -161,9 +164,11 @@
     include/pybind11/operators.h
     include/pybind11/pybind11.h
     include/pybind11/pytypes.h
+    include/pybind11/smart_holder.h
     include/pybind11/stl.h
     include/pybind11/stl_bind.h
     include/pybind11/stl/filesystem.h
+    include/pybind11/trampoline_self_life_support.h
     include/pybind11/type_caster_pyobject_ptr.h
     include/pybind11/typing.h
     include/pybind11/warnings.h)
diff --git a/MANIFEST.in b/MANIFEST.in
index 7ce83c5..ff9ee6c 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,6 @@
 prune tests
+prune ubench
+include README_smart_holder.rst
 recursive-include pybind11/include/pybind11 *.h
 recursive-include pybind11 *.py
 recursive-include pybind11 py.typed
diff --git a/README.rst b/README.rst
index 8e0c391..54da634 100644
--- a/README.rst
+++ b/README.rst
@@ -13,6 +13,10 @@
 
 .. start
 
+.. Note::
+
+   This is the pybind11 **smart_holder** branch. Please refer to
+   ``README_smart_holder.rst`` for branch-specific information.
 
 **pybind11** is a lightweight header-only library that exposes C++ types
 in Python and vice versa, mainly to create Python bindings of existing
diff --git a/README_smart_holder.rst b/README_smart_holder.rst
new file mode 100644
index 0000000..05b1ab5
--- /dev/null
+++ b/README_smart_holder.rst
@@ -0,0 +1,123 @@
+==============================
+pybind11 — smart_holder branch
+==============================
+
+
+Overview
+========
+
+- The smart_holder branch is a strict superset of the pybind11 master branch.
+  Everything that works with the master branch is expected to work exactly the
+  same with the smart_holder branch.
+
+- Activating the smart_holder functionality for a given C++ type ``T`` is as
+  easy as changing ``py::class_<T>`` to ``py::classh<T>`` in client code.
+
+- The ``py::classh<T>`` functionality includes
+
+  * support for **two-way** Python/C++ conversions for both
+    ``std::unique_ptr<T>`` and ``std::shared_ptr<T>`` **simultaneously**.
+    — In contrast, ``py::class_<T>`` only supports one-way C++-to-Python
+    conversions for ``std::unique_ptr<T>``, or alternatively two-way
+    Python/C++ conversions for ``std::shared_ptr<T>``, which then excludes
+    the one-way C++-to-Python ``std::unique_ptr<T>`` conversions (this
+    manifests itself through undefined runtime behavior).
+
+  * passing a Python object back to C++ via ``std::unique_ptr<T>``, safely
+    **disowning** the Python object.
+
+  * safely passing `"trampoline"
+    <https://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding-virtual-functions-in-python>`_
+    objects (objects with C++ virtual function overrides implemented in
+    Python) via ``std::unique_ptr<T>`` or ``std::shared_ptr<T>`` back to C++:
+    associated Python objects are automatically kept alive for the lifetime
+    of the smart-pointer.
+
+Note: As of `PR #5257 <https://github.com/pybind/pybind11/pull/5257>`_
+the smart_holder functionality is fully baked into pybind11.
+Prior to PR #5257 the smart_holder implementation was an "add-on", which made
+it necessary to use a ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro. This macro
+still exists for backward compatibility, but is now a no-op. The trade-off
+for this convenience is that the ``PYBIND11_INTERNALS_VERSION`` needed to be
+changed. Consequently, Python extension modules built with the smart_holder
+branch no longer interoperate with extension modules built with the pybind11
+master branch. If cross-extension-module interoperability is required, all
+extension modules involved must be built with the smart_holder branch.
+— Probably, most extension modules do not require cross-extension-module
+interoperability, but exceptions to this are quite common.
+
+
+What is fundamentally different?
+--------------------------------
+
+- Classic pybind11 has the concept of "smart-pointer is holder".
+  Interoperability between smart-pointers is completely missing. For example,
+  with ``py::class_<T, std::shared_ptr<T>>``, ``return``-ing a
+  ``std::unique_ptr<T>`` leads to undefined runtime behavior
+  (`#1138 <https://github.com/pybind/pybind11/issues/1138>`_).
+  A `systematic analysis can be found here
+  <https://github.com/pybind/pybind11/pull/2672#issuecomment-748392993>`_.
+
+- ``py::smart_holder`` has a richer concept in comparison, with well-defined
+  runtime behavior in all situations. ``py::smart_holder`` "knows" about both
+  ``std::unique_ptr<T>`` and ``std::shared_ptr<T>``, and how they interoperate.
+
+
+What motivated the development of the smart_holder code?
+--------------------------------------------------------
+
+- The original context was retooling of `PyCLIF
+  <https://github.com/google/clif/>`_, to use pybind11 underneath,
+  instead of directly targeting the Python C API. Essentially the smart_holder
+  branch is porting established PyCLIF functionality into pybind11. (However,
+  this work also led to bug fixes in PyCLIF.)
+
+
+Installation
+============
+
+Currently ``git clone`` is the only option. We do not have released packages.
+
+.. code-block:: bash
+
+   git clone --branch smart_holder https://github.com/pybind/pybind11.git
+
+Everything else is exactly identical to using the default (master) branch.
+
+
+Trampolines and std::unique_ptr
+-------------------------------
+
+A pybind11 `"trampoline"
+<https://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding-virtual-functions-in-python>`_
+is a C++ helper class with virtual function overrides that transparently
+call back from C++ into Python. To enable safely passing a ``std::unique_ptr``
+to a trampoline object between Python and C++, the trampoline class must
+inherit from ``py::trampoline_self_life_support``, for example:
+
+.. code-block:: cpp
+
+   class PyAnimal : public Animal, public py::trampoline_self_life_support {
+       ...
+   };
+
+This is the only difference compared to classic pybind11. A fairly
+minimal but complete example is tests/test_class_sh_trampoline_unique_ptr.cpp.
+
+
+Related links
+=============
+
+* The smart_holder branch addresses issue
+  `#1138 <https://github.com/pybind/pybind11/issues/1138>`_ and
+  the ten issues enumerated in the `description of PR 2839
+  <https://github.com/pybind/pybind11/pull/2839#issue-564808678>`_.
+
+* `Description of PR #2672
+  <https://github.com/pybind/pybind11/pull/2672#issue-522688184>`_, from which
+  the smart_holder branch was created.
+
+* Small `slide deck
+  <https://docs.google.com/presentation/d/1r7auDN0x-b6uf-XCvUnZz6z09raasRcCHBMVDh7PsnQ/>`_
+  presented in meeting with pybind11 maintainers on Feb 22, 2021. Slides 5
+  and 6 show performance comparisons. (These are outdated but probably not far off.)
diff --git a/docs/advanced/smart_ptrs.rst b/docs/advanced/smart_ptrs.rst
index b9f100c..e2e9f3f 100644
--- a/docs/advanced/smart_ptrs.rst
+++ b/docs/advanced/smart_ptrs.rst
@@ -1,6 +1,13 @@
 Smart pointers
 ##############
 
+.. Note::
+
+   This is the pybind11 **smart_holder** branch, but the information
+   below has NOT been updated accordingly yet. Please refer to
+   ``README_smart_holder.rst`` under the top-level pybind11 directory
+   for updated information about smart pointers.
+
 std::unique_ptr
 ===============
 
diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h
index eb15ffd..1b513f5 100644
--- a/include/pybind11/attr.h
+++ b/include/pybind11/attr.h
@@ -339,6 +339,10 @@
     /// Solves pybind/pybind11#1446
     bool release_gil_before_calling_cpp_dtor : 1;
 
+#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT
+    holder_enum_t holder_enum_v = holder_enum_t::undefined;
+#endif
+
     PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) {
         auto *base_info = detail::get_type_info(base, false);
         if (!base_info) {
diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h
index f2c0291..9ab547b 100644
--- a/include/pybind11/cast.h
+++ b/include/pybind11/cast.h
@@ -754,6 +754,7 @@
     static auto get(const T &p) -> decltype(p.get()) { return p.get(); }
 };
 
+// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to shared_ptr specialization.
 /// Type caster for holder types like std::shared_ptr, etc.
 /// The SFINAE hook is provided to help work around the current lack of support
 /// for smart-pointer interoperability. Please consider it an implementation
@@ -835,10 +836,145 @@
     holder_type holder;
 };
 
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+template <typename, typename SFINAE = void>
+struct copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled : std::true_type {};
+
+// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor copyable_holder_caster to reduce code duplication.
+template <typename type>
+struct copyable_holder_caster<
+    type,
+    std::shared_ptr<type>,
+    enable_if_t<copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled<type>::value>>
+    : public type_caster_base<type> {
+public:
+    using base = type_caster_base<type>;
+    static_assert(std::is_base_of<base, type_caster<type>>::value,
+                  "Holder classes are only supported for custom types");
+    using base::base;
+    using base::cast;
+    using base::typeinfo;
+    using base::value;
+
+    bool load(handle src, bool convert) {
+        if (base::template load_impl<copyable_holder_caster<type, std::shared_ptr<type>>>(
+                src, convert)) {
+            sh_load_helper.maybe_set_python_instance_is_alias(src);
+            return true;
+        }
+        return false;
+    }
+
+    explicit operator std::shared_ptr<type> *() {
+        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+            pybind11_fail("Passing `std::shared_ptr<T> *` from Python to C++ is not supported "
+                          "(inherently unsafe).");
+        }
+        return std::addressof(shared_ptr_storage);
+    }
+
+    explicit operator std::shared_ptr<type> &() {
+        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+            shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value);
+        }
+        return shared_ptr_storage;
+    }
+
+    static handle
+    cast(const std::shared_ptr<type> &src, return_value_policy policy, handle parent) {
+        const auto *ptr = src.get();
+        auto st = type_caster_base<type>::src_and_type(ptr);
+        if (st.second == nullptr) {
+            return handle(); // no type info: error will be set already
+        }
+        if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) {
+            return smart_holder_type_caster_support::smart_holder_from_shared_ptr(
+                src, policy, parent, st);
+        }
+        return type_caster_base<type>::cast_holder(ptr, &src);
+    }
+
+    // This function will succeed even if the `responsible_parent` does not own the
+    // wrapped C++ object directly.
+    // It is the responsibility of the caller to ensure that the `responsible_parent`
+    // has a `keep_alive` relationship with the owner of the wrapped C++ object, or
+    // that the wrapped C++ object lives for the duration of the process.
+    static std::shared_ptr<type> shared_ptr_with_responsible_parent(handle responsible_parent) {
+        copyable_holder_caster loader;
+        loader.load(responsible_parent, /*convert=*/false);
+        assert(loader.typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder);
+        return loader.sh_load_helper.load_as_shared_ptr(loader.value, responsible_parent);
+    }
+
+protected:
+    friend class type_caster_generic;
+    void check_holder_compat() {
+        if (typeinfo->default_holder) {
+            throw cast_error("Unable to load a custom holder type from a default-holder instance");
+        }
+    }
+
+    void load_value(value_and_holder &&v_h) {
+        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+            sh_load_helper.loaded_v_h = v_h;
+            sh_load_helper.was_populated = true;
+            value = sh_load_helper.get_void_ptr_or_nullptr();
+            return;
+        }
+        if (v_h.holder_constructed()) {
+            value = v_h.value_ptr();
+            shared_ptr_storage = v_h.template holder<std::shared_ptr<type>>();
+            return;
+        }
+        throw cast_error("Unable to cast from non-held to held instance (T& to Holder<T>) "
+#    if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
+                         "(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for "
+                         "type information)");
+#    else
+                         "of type '"
+                         + type_id<std::shared_ptr<type>>() + "''");
+#    endif
+    }
+
+    template <typename T = std::shared_ptr<type>,
+              detail::enable_if_t<!std::is_constructible<T, const T &, type *>::value, int> = 0>
+    bool try_implicit_casts(handle, bool) {
+        return false;
+    }
+
+    template <typename T = std::shared_ptr<type>,
+              detail::enable_if_t<std::is_constructible<T, const T &, type *>::value, int> = 0>
+    bool try_implicit_casts(handle src, bool convert) {
+        for (auto &cast : typeinfo->implicit_casts) {
+            copyable_holder_caster sub_caster(*cast.first);
+            if (sub_caster.load(src, convert)) {
+                value = cast.second(sub_caster.value);
+                if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+                    sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h;
+                } else {
+                    shared_ptr_storage
+                        = std::shared_ptr<type>(sub_caster.shared_ptr_storage, (type *) value);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static bool try_direct_conversions(handle) { return false; }
+
+    smart_holder_type_caster_support::load_helper<remove_cv_t<type>> sh_load_helper; // Const2Mutbl
+    std::shared_ptr<type> shared_ptr_storage;
+};
+
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+
 /// Specialize for the common std::shared_ptr, so users don't need to
 template <typename T>
 class type_caster<std::shared_ptr<T>> : public copyable_holder_caster<T, std::shared_ptr<T>> {};
 
+// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to unique_ptr specialization.
 /// Type caster for holder types like std::unique_ptr.
 /// Please consider the SFINAE hook an implementation detail, as explained
 /// in the comment for the copyable_holder_caster.
@@ -854,6 +990,147 @@
     static constexpr auto name = type_caster_base<type>::name;
 };
 
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+template <typename, typename SFINAE = void>
+struct move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled : std::true_type {};
+
+// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor move_only_holder_caster to reduce code duplication.
+template <typename type, typename deleter>
+struct move_only_holder_caster<
+    type,
+    std::unique_ptr<type, deleter>,
+    enable_if_t<move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled<type>::value>>
+    : public type_caster_base<type> {
+public:
+    using base = type_caster_base<type>;
+    static_assert(std::is_base_of<base, type_caster<type>>::value,
+                  "Holder classes are only supported for custom types");
+    using base::base;
+    using base::cast;
+    using base::typeinfo;
+    using base::value;
+
+    static handle
+    cast(std::unique_ptr<type, deleter> &&src, return_value_policy policy, handle parent) {
+        auto *ptr = src.get();
+        auto st = type_caster_base<type>::src_and_type(ptr);
+        if (st.second == nullptr) {
+            return handle(); // no type info: error will be set already
+        }
+        if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) {
+            return smart_holder_type_caster_support::smart_holder_from_unique_ptr(
+                std::move(src), policy, parent, st);
+        }
+        return type_caster_generic::cast(st.first,
+                                         return_value_policy::take_ownership,
+                                         {},
+                                         st.second,
+                                         nullptr,
+                                         nullptr,
+                                         std::addressof(src));
+    }
+
+    static handle
+    cast(const std::unique_ptr<type, deleter> &src, return_value_policy policy, handle parent) {
+        if (!src) {
+            return none().release();
+        }
+        if (policy == return_value_policy::automatic) {
+            policy = return_value_policy::reference_internal;
+        }
+        if (policy != return_value_policy::reference_internal) {
+            throw cast_error("Invalid return_value_policy for const unique_ptr&");
+        }
+        return type_caster_base<type>::cast(src.get(), policy, parent);
+    }
+
+    bool load(handle src, bool convert) {
+        if (base::template load_impl<
+                move_only_holder_caster<type, std::unique_ptr<type, deleter>>>(src, convert)) {
+            sh_load_helper.maybe_set_python_instance_is_alias(src);
+            return true;
+        }
+        return false;
+    }
+
+    void load_value(value_and_holder &&v_h) {
+        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+            sh_load_helper.loaded_v_h = v_h;
+            sh_load_helper.loaded_v_h.type = typeinfo;
+            sh_load_helper.was_populated = true;
+            value = sh_load_helper.get_void_ptr_or_nullptr();
+            return;
+        }
+        pybind11_fail(
+            "Passing `std::unique_ptr<T>` from Python to C++ requires `py::classh` (with T = "
+            + clean_type_id(typeinfo->cpptype->name()) + ")");
+    }
+
+    template <typename T_>
+    using cast_op_type
+        = conditional_t<std::is_same<typename std::remove_volatile<T_>::type,
+                                     const std::unique_ptr<type, deleter> &>::value
+                            || std::is_same<typename std::remove_volatile<T_>::type,
+                                            const std::unique_ptr<const type, deleter> &>::value,
+                        const std::unique_ptr<type, deleter> &,
+                        std::unique_ptr<type, deleter>>;
+
+    explicit operator std::unique_ptr<type, deleter>() {
+        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+            return sh_load_helper.template load_as_unique_ptr<deleter>(value);
+        }
+        pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__));
+    }
+
+    explicit operator const std::unique_ptr<type, deleter> &() {
+        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+            // Get shared_ptr to ensure that the Python object is not disowned elsewhere.
+            shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value);
+            // Build a temporary unique_ptr that is meant to never expire.
+            unique_ptr_storage = std::shared_ptr<std::unique_ptr<type, deleter>>(
+                new std::unique_ptr<type, deleter>{
+                    sh_load_helper.template load_as_const_unique_ptr<deleter>(
+                        shared_ptr_storage.get())},
+                [](std::unique_ptr<type, deleter> *ptr) {
+                    if (!ptr) {
+                        pybind11_fail("FATAL: `const std::unique_ptr<T, D> &` was disowned "
+                                      "(EXPECT UNDEFINED BEHAVIOR).");
+                    }
+                    (void) ptr->release();
+                    delete ptr;
+                });
+            return *unique_ptr_storage;
+        }
+        pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__));
+    }
+
+    bool try_implicit_casts(handle src, bool convert) {
+        for (auto &cast : typeinfo->implicit_casts) {
+            move_only_holder_caster sub_caster(*cast.first);
+            if (sub_caster.load(src, convert)) {
+                value = cast.second(sub_caster.value);
+                if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+                    sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h;
+                } else {
+                    pybind11_fail("Expected to be UNREACHABLE: " __FILE__
+                                  ":" PYBIND11_TOSTRING(__LINE__));
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static bool try_direct_conversions(handle) { return false; }
+
+    smart_holder_type_caster_support::load_helper<remove_cv_t<type>> sh_load_helper; // Const2Mutbl
+    std::shared_ptr<type> shared_ptr_storage; // Serves as a pseudo lock.
+    std::shared_ptr<std::unique_ptr<type, deleter>> unique_ptr_storage;
+};
+
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+
 template <typename type, typename deleter>
 class type_caster<std::unique_ptr<type, deleter>>
     : public move_only_holder_caster<type, std::unique_ptr<type, deleter>> {};
@@ -887,10 +1164,16 @@
 template <typename base, typename holder>
 struct is_holder_type
     : std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>> {};
-// Specialization for always-supported unique_ptr holders:
+
+// Specializations for always-supported holders:
 template <typename base, typename deleter>
 struct is_holder_type<base, std::unique_ptr<base, deleter>> : std::true_type {};
 
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+template <typename base>
+struct is_holder_type<base, smart_holder> : std::true_type {};
+#endif
+
 #ifdef PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION // See PR #4888
 
 // This leads to compilation errors if a specialization is missing.
diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h
index aac6e84..08e23af 100644
--- a/include/pybind11/detail/class.h
+++ b/include/pybind11/detail/class.h
@@ -457,6 +457,8 @@
             if (instance->owned || v_h.holder_constructed()) {
                 v_h.type->dealloc(v_h);
             }
+        } else if (v_h.holder_constructed()) {
+            v_h.type->dealloc(v_h); // Disowned instance.
         }
     }
     // Deallocate the value/holder layout internals:
diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h
index 372dd06..f119474 100644
--- a/include/pybind11/detail/common.h
+++ b/include/pybind11/detail/common.h
@@ -605,6 +605,11 @@
     bool simple_instance_registered : 1;
     /// If true, get_internals().patients has an entry for this object
     bool has_patients : 1;
+// Cannot use PYBIND11_INTERNALS_VERSION >= 106 here without refactoring.
+#if PYBIND11_VERSION_MAJOR >= 3
+    /// If true, this Python object needs to be kept alive for the lifetime of the C++ value.
+    bool is_alias : 1;
+#endif
 
     /// Initializes all of the above type/values/holders data (but not the instance values
     /// themselves)
diff --git a/include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h b/include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
new file mode 100644
index 0000000..7c00fe9
--- /dev/null
+++ b/include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2021 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+#include "common.h"
+
+#include <type_traits>
+
+PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+PYBIND11_NAMESPACE_BEGIN(detail)
+
+template <typename To, typename From, typename SFINAE = void>
+struct dynamic_raw_ptr_cast_is_possible : std::false_type {};
+
+template <typename To, typename From>
+struct dynamic_raw_ptr_cast_is_possible<
+    To,
+    From,
+    detail::enable_if_t<!std::is_same<To, void>::value && std::is_polymorphic<From>::value>>
+    : std::true_type {};
+
+template <typename To,
+          typename From,
+          detail::enable_if_t<!dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
+To *dynamic_raw_ptr_cast_if_possible(From * /*ptr*/) {
+    return nullptr;
+}
+
+template <typename To,
+          typename From,
+          detail::enable_if_t<dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
+To *dynamic_raw_ptr_cast_if_possible(From *ptr) {
+    return dynamic_cast<To *>(ptr);
+}
+
+PYBIND11_NAMESPACE_END(detail)
+PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h
index 3eeeaaf..8fba2f8 100644
--- a/include/pybind11/detail/init.h
+++ b/include/pybind11/detail/init.h
@@ -10,6 +10,7 @@
 #pragma once
 
 #include "class.h"
+#include "using_smart_holder.h"
 
 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
 
@@ -155,7 +156,7 @@
 // holder.  This also handles types like std::shared_ptr<T> and std::unique_ptr<T> where T is a
 // derived type (through those holder's implicit conversion from derived class holder
 // constructors).
-template <typename Class>
+template <typename Class, detail::enable_if_t<!is_smart_holder<Holder<Class>>::value, int> = 0>
 void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
     PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
     auto *ptr = holder_helper<Holder<Class>>::get(holder);
@@ -197,6 +198,78 @@
     v_h.value_ptr() = new Alias<Class>(std::move(result));
 }
 
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+template <typename T, typename D>
+smart_holder init_smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr,
+                                               bool void_cast_raw_ptr) {
+    void *void_ptr = void_cast_raw_ptr ? static_cast<void *>(unq_ptr.get()) : nullptr;
+    return smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr);
+}
+
+template <typename Class,
+          typename D = std::default_delete<Cpp<Class>>,
+          detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
+void construct(value_and_holder &v_h, std::unique_ptr<Cpp<Class>, D> &&unq_ptr, bool need_alias) {
+    PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
+    auto *ptr = unq_ptr.get();
+    no_nullptr(ptr);
+    if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
+        throw type_error("pybind11::init(): construction failed: returned std::unique_ptr pointee "
+                         "is not an alias instance");
+    }
+    // Here and below: if the new object is a trampoline, the shared_from_this mechanism needs
+    // to be prevented from accessing the smart_holder vptr, because it does not keep the
+    // trampoline Python object alive. For types that don't inherit from enable_shared_from_this
+    // it does not matter if void_cast_raw_ptr is true or false, therefore it's not necessary
+    // to also inspect the type.
+    auto smhldr = init_smart_holder_from_unique_ptr(
+        std::move(unq_ptr), /*void_cast_raw_ptr*/ Class::has_alias && is_alias<Class>(ptr));
+    v_h.value_ptr() = ptr;
+    v_h.type->init_instance(v_h.inst, &smhldr);
+}
+
+template <typename Class,
+          typename D = std::default_delete<Alias<Class>>,
+          detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
+void construct(value_and_holder &v_h,
+               std::unique_ptr<Alias<Class>, D> &&unq_ptr,
+               bool /*need_alias*/) {
+    auto *ptr = unq_ptr.get();
+    no_nullptr(ptr);
+    auto smhldr
+        = init_smart_holder_from_unique_ptr(std::move(unq_ptr), /*void_cast_raw_ptr*/ true);
+    v_h.value_ptr() = ptr;
+    v_h.type->init_instance(v_h.inst, &smhldr);
+}
+
+template <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
+void construct(value_and_holder &v_h, std::shared_ptr<Cpp<Class>> &&shd_ptr, bool need_alias) {
+    PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
+    auto *ptr = shd_ptr.get();
+    no_nullptr(ptr);
+    if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
+        throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee "
+                         "is not an alias instance");
+    }
+    auto smhldr = smart_holder::from_shared_ptr(shd_ptr);
+    v_h.value_ptr() = ptr;
+    v_h.type->init_instance(v_h.inst, &smhldr);
+}
+
+template <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
+void construct(value_and_holder &v_h,
+               std::shared_ptr<Alias<Class>> &&shd_ptr,
+               bool /*need_alias*/) {
+    auto *ptr = shd_ptr.get();
+    no_nullptr(ptr);
+    auto smhldr = smart_holder::from_shared_ptr(shd_ptr);
+    v_h.value_ptr() = ptr;
+    v_h.type->init_instance(v_h.inst, &smhldr);
+}
+
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+
 // Implementing class for py::init<...>()
 template <typename... Args>
 struct constructor {
diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h
index dffb9e4..beba642 100644
--- a/include/pybind11/detail/internals.h
+++ b/include/pybind11/detail/internals.h
@@ -37,7 +37,11 @@
 /// further ABI-incompatible changes may be made before the ABI is officially
 /// changed to the new version.
 #ifndef PYBIND11_INTERNALS_VERSION
-#    define PYBIND11_INTERNALS_VERSION 6
+#    if PYBIND11_VERSION_MAJOR >= 3
+#        define PYBIND11_INTERNALS_VERSION 106
+#    else
+#        define PYBIND11_INTERNALS_VERSION 6
+#    endif
 #endif
 
 #if PYBIND11_INTERNALS_VERSION < 6
@@ -211,6 +215,25 @@
     }
 };
 
+#if PYBIND11_INTERNALS_VERSION >= 106
+
+#    define PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT
+
+enum class holder_enum_t : uint8_t {
+    undefined,
+    std_unique_ptr, // Default, lacking interop with std::shared_ptr.
+    std_shared_ptr, // Lacking interop with std::unique_ptr.
+    smart_holder,   // Full std::unique_ptr / std::shared_ptr interop.
+    custom_holder,
+};
+
+#endif
+
+#if defined(PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT)                                     \
+    && !defined(PYBIND11_SMART_HOLDER_DISABLE)
+#    define PYBIND11_SMART_HOLDER_ENABLED
+#endif
+
 /// Additional type information which does not fit into the PyTypeObject.
 /// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`.
 struct type_info {
@@ -234,9 +257,14 @@
     /* True if there is no multiple inheritance in this type's inheritance tree */
     bool simple_ancestors : 1;
     /* for base vs derived holder_type checks */
+    // SMART_HOLDER_BAKEIN_FOLLOW_ON: Remove default_holder member here and
+    // produce better error messages in the places where it is currently used.
     bool default_holder : 1;
     /* true if this is a type registered with py::module_local */
     bool module_local : 1;
+#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT
+    holder_enum_t holder_enum_v = holder_enum_t::undefined;
+#endif
 };
 
 #define PYBIND11_INTERNALS_ID                                                                     \
diff --git a/include/pybind11/detail/struct_smart_holder.h b/include/pybind11/detail/struct_smart_holder.h
new file mode 100644
index 0000000..980fc36
--- /dev/null
+++ b/include/pybind11/detail/struct_smart_holder.h
@@ -0,0 +1,349 @@
+// Copyright (c) 2020-2024 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/* Proof-of-Concept for smart pointer interoperability.
+
+High-level aspects:
+
+* Support all `unique_ptr`, `shared_ptr` interops that are feasible.
+
+* Cleanly and clearly report all interops that are infeasible.
+
+* Meant to fit into a `PyObject`, as a holder for C++ objects.
+
+* Support a system design that makes it impossible to trigger
+  C++ Undefined Behavior, especially from Python.
+
+* Support a system design with clean runtime inheritance casting. From this
+  it follows that the `smart_holder` needs to be type-erased (`void*`).
+
+* Handling of RTTI for the type-erased held pointer is NOT implemented here.
+  It is the responsibility of the caller to ensure that `static_cast<T *>`
+  is well-formed when calling `as_*` member functions. Inheritance casting
+  needs to be handled in a different layer (similar to the code organization
+  in boost/python/object/inheritance.hpp).
+
+Details:
+
+* The "root holder" chosen here is a `shared_ptr<void>` (named `vptr` in this
+  implementation). This choice is practically inevitable because `shared_ptr`
+  has only very limited support for inspecting and accessing its deleter.
+
+* If created from a raw pointer, or a `unique_ptr` without a custom deleter,
+  `vptr` always uses a custom deleter, to support `unique_ptr`-like disowning.
+  The custom deleters could be extended to included life-time management for
+  external objects (e.g. `PyObject`).
+
+* If created from an external `shared_ptr`, or a `unique_ptr` with a custom
+  deleter, including life-time management for external objects is infeasible.
+
+* By choice, the smart_holder is movable but not copyable, to keep the design
+  simple, and to guard against accidental copying overhead.
+
+* The `void_cast_raw_ptr` option is needed to make the `smart_holder` `vptr`
+  member invisible to the `shared_from_this` mechanism, in case the lifetime
+  of a `PyObject` is tied to the pointee.
+*/
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <typeinfo>
+#include <utility>
+
+// pybindit = Python Bindings Innovation Track.
+// Currently not in pybind11 namespace to signal that this POC does not depend
+// on any existing pybind11 functionality.
+namespace pybindit {
+namespace memory {
+
+static constexpr bool type_has_shared_from_this(...) { return false; }
+
+template <typename T>
+static constexpr bool type_has_shared_from_this(const std::enable_shared_from_this<T> *) {
+    return true;
+}
+
+struct guarded_delete {
+    std::weak_ptr<void> released_ptr;    // Trick to keep the smart_holder memory footprint small.
+    std::function<void(void *)> del_fun; // Rare case.
+    void (*del_ptr)(void *);             // Common case.
+    bool use_del_fun;
+    bool armed_flag;
+    guarded_delete(std::function<void(void *)> &&del_fun, bool armed_flag)
+        : del_fun{std::move(del_fun)}, del_ptr{nullptr}, use_del_fun{true},
+          armed_flag{armed_flag} {}
+    guarded_delete(void (*del_ptr)(void *), bool armed_flag)
+        : del_ptr{del_ptr}, use_del_fun{false}, armed_flag{armed_flag} {}
+    void operator()(void *raw_ptr) const {
+        if (armed_flag) {
+            if (use_del_fun) {
+                del_fun(raw_ptr);
+            } else {
+                del_ptr(raw_ptr);
+            }
+        }
+    }
+};
+
+template <typename T, typename std::enable_if<std::is_destructible<T>::value, int>::type = 0>
+inline void builtin_delete_if_destructible(void *raw_ptr) {
+    std::default_delete<T>{}(static_cast<T *>(raw_ptr));
+}
+
+template <typename T, typename std::enable_if<!std::is_destructible<T>::value, int>::type = 0>
+inline void builtin_delete_if_destructible(void *) {
+    // This noop operator is needed to avoid a compilation error (for `delete raw_ptr;`), but
+    // throwing an exception from a destructor will std::terminate the process. Therefore the
+    // runtime check for lifetime-management correctness is implemented elsewhere (in
+    // ensure_pointee_is_destructible()).
+}
+
+template <typename T>
+guarded_delete make_guarded_builtin_delete(bool armed_flag) {
+    return guarded_delete(builtin_delete_if_destructible<T>, armed_flag);
+}
+
+template <typename T, typename D>
+struct custom_deleter {
+    D deleter;
+    explicit custom_deleter(D &&deleter) : deleter{std::forward<D>(deleter)} {}
+    void operator()(void *raw_ptr) { deleter(static_cast<T *>(raw_ptr)); }
+};
+
+template <typename T, typename D>
+guarded_delete make_guarded_custom_deleter(D &&uqp_del, bool armed_flag) {
+    return guarded_delete(
+        std::function<void(void *)>(custom_deleter<T, D>(std::forward<D>(uqp_del))), armed_flag);
+}
+
+template <typename T>
+inline bool is_std_default_delete(const std::type_info &rtti_deleter) {
+    return rtti_deleter == typeid(std::default_delete<T>)
+           || rtti_deleter == typeid(std::default_delete<T const>);
+}
+
+struct smart_holder {
+    const std::type_info *rtti_uqp_del = nullptr;
+    std::shared_ptr<void> vptr;
+    bool vptr_is_using_noop_deleter : 1;
+    bool vptr_is_using_builtin_delete : 1;
+    bool vptr_is_external_shared_ptr : 1;
+    bool is_populated : 1;
+    bool is_disowned : 1;
+
+    // Design choice: smart_holder is movable but not copyable.
+    smart_holder(smart_holder &&) = default;
+    smart_holder(const smart_holder &) = delete;
+    smart_holder &operator=(smart_holder &&) = delete;
+    smart_holder &operator=(const smart_holder &) = delete;
+
+    smart_holder()
+        : vptr_is_using_noop_deleter{false}, vptr_is_using_builtin_delete{false},
+          vptr_is_external_shared_ptr{false}, is_populated{false}, is_disowned{false} {}
+
+    bool has_pointee() const { return vptr != nullptr; }
+
+    template <typename T>
+    static void ensure_pointee_is_destructible(const char *context) {
+        if (!std::is_destructible<T>::value) {
+            throw std::invalid_argument(std::string("Pointee is not destructible (") + context
+                                        + ").");
+        }
+    }
+
+    void ensure_is_populated(const char *context) const {
+        if (!is_populated) {
+            throw std::runtime_error(std::string("Unpopulated holder (") + context + ").");
+        }
+    }
+    void ensure_is_not_disowned(const char *context) const {
+        if (is_disowned) {
+            throw std::runtime_error(std::string("Holder was disowned already (") + context
+                                     + ").");
+        }
+    }
+
+    void ensure_vptr_is_using_builtin_delete(const char *context) const {
+        if (vptr_is_external_shared_ptr) {
+            throw std::invalid_argument(std::string("Cannot disown external shared_ptr (")
+                                        + context + ").");
+        }
+        if (vptr_is_using_noop_deleter) {
+            throw std::invalid_argument(std::string("Cannot disown non-owning holder (") + context
+                                        + ").");
+        }
+        if (!vptr_is_using_builtin_delete) {
+            throw std::invalid_argument(std::string("Cannot disown custom deleter (") + context
+                                        + ").");
+        }
+    }
+
+    template <typename T, typename D>
+    void ensure_compatible_rtti_uqp_del(const char *context) const {
+        const std::type_info *rtti_requested = &typeid(D);
+        if (!rtti_uqp_del) {
+            if (!is_std_default_delete<T>(*rtti_requested)) {
+                throw std::invalid_argument(std::string("Missing unique_ptr deleter (") + context
+                                            + ").");
+            }
+            ensure_vptr_is_using_builtin_delete(context);
+        } else if (!(*rtti_requested == *rtti_uqp_del)
+                   && !(vptr_is_using_builtin_delete
+                        && is_std_default_delete<T>(*rtti_requested))) {
+            throw std::invalid_argument(std::string("Incompatible unique_ptr deleter (") + context
+                                        + ").");
+        }
+    }
+
+    void ensure_has_pointee(const char *context) const {
+        if (!has_pointee()) {
+            throw std::invalid_argument(std::string("Disowned holder (") + context + ").");
+        }
+    }
+
+    void ensure_use_count_1(const char *context) const {
+        if (vptr == nullptr) {
+            throw std::invalid_argument(std::string("Cannot disown nullptr (") + context + ").");
+        }
+        // In multithreaded environments accessing use_count can lead to
+        // race conditions, but in the context of Python it is a bug (elsewhere)
+        // if the Global Interpreter Lock (GIL) is not being held when this code
+        // is reached.
+        // PYBIND11:REMINDER: This may need to be protected by a mutex in free-threaded Python.
+        if (vptr.use_count() != 1) {
+            throw std::invalid_argument(std::string("Cannot disown use_count != 1 (") + context
+                                        + ").");
+        }
+    }
+
+    void reset_vptr_deleter_armed_flag(bool armed_flag) const {
+        auto *vptr_del_ptr = std::get_deleter<guarded_delete>(vptr);
+        if (vptr_del_ptr == nullptr) {
+            throw std::runtime_error(
+                "smart_holder::reset_vptr_deleter_armed_flag() called in an invalid context.");
+        }
+        vptr_del_ptr->armed_flag = armed_flag;
+    }
+
+    // Caller is responsible for precondition: ensure_compatible_rtti_uqp_del<T, D>() must succeed.
+    template <typename T, typename D>
+    std::unique_ptr<D> extract_deleter(const char *context) const {
+        const auto *gd = std::get_deleter<guarded_delete>(vptr);
+        if (gd && gd->use_del_fun) {
+            const auto &custom_deleter_ptr = gd->del_fun.template target<custom_deleter<T, D>>();
+            if (custom_deleter_ptr == nullptr) {
+                throw std::runtime_error(
+                    std::string("smart_holder::extract_deleter() precondition failure (") + context
+                    + ").");
+            }
+            static_assert(std::is_copy_constructible<D>::value,
+                          "Required for compatibility with smart_holder functionality.");
+            return std::unique_ptr<D>(new D(custom_deleter_ptr->deleter));
+        }
+        return nullptr;
+    }
+
+    static smart_holder from_raw_ptr_unowned(void *raw_ptr) {
+        smart_holder hld;
+        hld.vptr.reset(raw_ptr, [](void *) {});
+        hld.vptr_is_using_noop_deleter = true;
+        hld.is_populated = true;
+        return hld;
+    }
+
+    template <typename T>
+    T *as_raw_ptr_unowned() const {
+        return static_cast<T *>(vptr.get());
+    }
+
+    template <typename T>
+    static smart_holder from_raw_ptr_take_ownership(T *raw_ptr, bool void_cast_raw_ptr = false) {
+        ensure_pointee_is_destructible<T>("from_raw_ptr_take_ownership");
+        smart_holder hld;
+        auto gd = make_guarded_builtin_delete<T>(true);
+        if (void_cast_raw_ptr) {
+            hld.vptr.reset(static_cast<void *>(raw_ptr), std::move(gd));
+        } else {
+            hld.vptr.reset(raw_ptr, std::move(gd));
+        }
+        hld.vptr_is_using_builtin_delete = true;
+        hld.is_populated = true;
+        return hld;
+    }
+
+    // Caller is responsible for ensuring the complex preconditions
+    // (see `smart_holder_type_caster_support::load_helper`).
+    void disown() {
+        reset_vptr_deleter_armed_flag(false);
+        is_disowned = true;
+    }
+
+    // Caller is responsible for ensuring the complex preconditions
+    // (see `smart_holder_type_caster_support::load_helper`).
+    void reclaim_disowned() {
+        reset_vptr_deleter_armed_flag(true);
+        is_disowned = false;
+    }
+
+    // Caller is responsible for ensuring the complex preconditions
+    // (see `smart_holder_type_caster_support::load_helper`).
+    void release_disowned() { vptr.reset(); }
+
+    void ensure_can_release_ownership(const char *context = "ensure_can_release_ownership") const {
+        ensure_is_not_disowned(context);
+        ensure_vptr_is_using_builtin_delete(context);
+        ensure_use_count_1(context);
+    }
+
+    // Caller is responsible for ensuring the complex preconditions
+    // (see `smart_holder_type_caster_support::load_helper`).
+    void release_ownership() {
+        reset_vptr_deleter_armed_flag(false);
+        release_disowned();
+    }
+
+    template <typename T, typename D>
+    static smart_holder from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr,
+                                        void *void_ptr = nullptr) {
+        smart_holder hld;
+        hld.rtti_uqp_del = &typeid(D);
+        hld.vptr_is_using_builtin_delete = is_std_default_delete<T>(*hld.rtti_uqp_del);
+        guarded_delete gd{nullptr, false};
+        if (hld.vptr_is_using_builtin_delete) {
+            gd = make_guarded_builtin_delete<T>(true);
+        } else {
+            gd = make_guarded_custom_deleter<T, D>(std::move(unq_ptr.get_deleter()), true);
+        }
+        if (void_ptr != nullptr) {
+            hld.vptr.reset(void_ptr, std::move(gd));
+        } else {
+            hld.vptr.reset(unq_ptr.get(), std::move(gd));
+        }
+        (void) unq_ptr.release();
+        hld.is_populated = true;
+        return hld;
+    }
+
+    template <typename T>
+    static smart_holder from_shared_ptr(std::shared_ptr<T> shd_ptr) {
+        smart_holder hld;
+        hld.vptr = std::static_pointer_cast<void>(shd_ptr);
+        hld.vptr_is_external_shared_ptr = true;
+        hld.is_populated = true;
+        return hld;
+    }
+
+    template <typename T>
+    std::shared_ptr<T> as_shared_ptr() const {
+        return std::static_pointer_cast<T>(vptr);
+    }
+};
+
+} // namespace memory
+} // namespace pybindit
diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h
index 8751364..097d176 100644
--- a/include/pybind11/detail/type_caster_base.h
+++ b/include/pybind11/detail/type_caster_base.h
@@ -9,13 +9,17 @@
 
 #pragma once
 
+#include <pybind11/gil.h>
 #include <pybind11/pytypes.h>
+#include <pybind11/trampoline_self_life_support.h>
 
 #include "common.h"
 #include "cpp_conduit.h"
 #include "descr.h"
+#include "dynamic_raw_ptr_cast_if_possible.h"
 #include "internals.h"
 #include "typeid.h"
+#include "using_smart_holder.h"
 #include "value_and_holder.h"
 
 #include <cstdint>
@@ -507,6 +511,365 @@
 void keep_alive_impl(handle nurse, handle patient);
 inline PyObject *make_new_instance(PyTypeObject *type);
 
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+// PYBIND11:REMINDER: Needs refactoring of existing pybind11 code.
+inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo);
+
+PYBIND11_NAMESPACE_BEGIN(smart_holder_type_caster_support)
+
+struct value_and_holder_helper {
+    value_and_holder loaded_v_h;
+
+    bool have_holder() const {
+        return loaded_v_h.vh != nullptr && loaded_v_h.holder_constructed();
+    }
+
+    smart_holder &holder() const { return loaded_v_h.holder<smart_holder>(); }
+
+    void throw_if_uninitialized_or_disowned_holder(const char *typeid_name) const {
+        static const std::string missing_value_msg = "Missing value for wrapped C++ type `";
+        if (!holder().is_populated) {
+            throw value_error(missing_value_msg + clean_type_id(typeid_name)
+                              + "`: Python instance is uninitialized.");
+        }
+        if (!holder().has_pointee()) {
+            throw value_error(missing_value_msg + clean_type_id(typeid_name)
+                              + "`: Python instance was disowned.");
+        }
+    }
+
+    void throw_if_uninitialized_or_disowned_holder(const std::type_info &type_info) const {
+        throw_if_uninitialized_or_disowned_holder(type_info.name());
+    }
+
+    // have_holder() must be true or this function will fail.
+    void throw_if_instance_is_currently_owned_by_shared_ptr() const {
+        auto *vptr_gd_ptr = std::get_deleter<pybindit::memory::guarded_delete>(holder().vptr);
+        if (vptr_gd_ptr != nullptr && !vptr_gd_ptr->released_ptr.expired()) {
+            throw value_error("Python instance is currently owned by a std::shared_ptr.");
+        }
+    }
+
+    void *get_void_ptr_or_nullptr() const {
+        if (have_holder()) {
+            auto &hld = holder();
+            if (hld.is_populated && hld.has_pointee()) {
+                return hld.template as_raw_ptr_unowned<void>();
+            }
+        }
+        return nullptr;
+    }
+};
+
+template <typename T, typename D>
+handle smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&src,
+                                    return_value_policy policy,
+                                    handle parent,
+                                    const std::pair<const void *, const type_info *> &st) {
+    if (policy == return_value_policy::copy) {
+        throw cast_error("return_value_policy::copy is invalid for unique_ptr.");
+    }
+    if (!src) {
+        return none().release();
+    }
+    void *src_raw_void_ptr = const_cast<void *>(st.first);
+    assert(st.second != nullptr);
+    const detail::type_info *tinfo = st.second;
+    if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) {
+        auto *self_life_support
+            = dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(src.get());
+        if (self_life_support != nullptr) {
+            value_and_holder &v_h = self_life_support->v_h;
+            if (v_h.inst != nullptr && v_h.vh != nullptr) {
+                auto &holder = v_h.holder<smart_holder>();
+                if (!holder.is_disowned) {
+                    pybind11_fail("smart_holder_from_unique_ptr: unexpected "
+                                  "smart_holder.is_disowned failure.");
+                }
+                // Critical transfer-of-ownership section. This must stay together.
+                self_life_support->deactivate_life_support();
+                holder.reclaim_disowned();
+                (void) src.release();
+                // Critical section end.
+                return existing_inst;
+            }
+        }
+        throw cast_error("Invalid unique_ptr: another instance owns this pointer already.");
+    }
+
+    auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type));
+    auto *inst_raw_ptr = reinterpret_cast<instance *>(inst.ptr());
+    inst_raw_ptr->owned = true;
+    void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr();
+    valueptr = src_raw_void_ptr;
+
+    if (static_cast<void *>(src.get()) == src_raw_void_ptr) {
+        // This is a multiple-inheritance situation that is incompatible with the current
+        // shared_from_this handling (see PR #3023). Is there a better solution?
+        src_raw_void_ptr = nullptr;
+    }
+    auto smhldr = smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr);
+    tinfo->init_instance(inst_raw_ptr, static_cast<const void *>(&smhldr));
+
+    if (policy == return_value_policy::reference_internal) {
+        keep_alive_impl(inst, parent);
+    }
+
+    return inst.release();
+}
+
+template <typename T, typename D>
+handle smart_holder_from_unique_ptr(std::unique_ptr<T const, D> &&src,
+                                    return_value_policy policy,
+                                    handle parent,
+                                    const std::pair<const void *, const type_info *> &st) {
+    return smart_holder_from_unique_ptr(
+        std::unique_ptr<T, D>(const_cast<T *>(src.release()),
+                              std::move(src.get_deleter())), // Const2Mutbl
+        policy,
+        parent,
+        st);
+}
+
+template <typename T>
+handle smart_holder_from_shared_ptr(const std::shared_ptr<T> &src,
+                                    return_value_policy policy,
+                                    handle parent,
+                                    const std::pair<const void *, const type_info *> &st) {
+    switch (policy) {
+        case return_value_policy::automatic:
+        case return_value_policy::automatic_reference:
+            break;
+        case return_value_policy::take_ownership:
+            throw cast_error("Invalid return_value_policy for shared_ptr (take_ownership).");
+        case return_value_policy::copy:
+        case return_value_policy::move:
+            break;
+        case return_value_policy::reference:
+            throw cast_error("Invalid return_value_policy for shared_ptr (reference).");
+        case return_value_policy::reference_internal:
+            break;
+    }
+    if (!src) {
+        return none().release();
+    }
+
+    auto src_raw_ptr = src.get();
+    assert(st.second != nullptr);
+    void *src_raw_void_ptr = static_cast<void *>(src_raw_ptr);
+    const detail::type_info *tinfo = st.second;
+    if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) {
+        // PYBIND11:REMINDER: MISSING: Enforcement of consistency with existing smart_holder.
+        // PYBIND11:REMINDER: MISSING: keep_alive.
+        return existing_inst;
+    }
+
+    auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type));
+    auto *inst_raw_ptr = reinterpret_cast<instance *>(inst.ptr());
+    inst_raw_ptr->owned = true;
+    void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr();
+    valueptr = src_raw_void_ptr;
+
+    auto smhldr
+        = smart_holder::from_shared_ptr(std::shared_ptr<void>(src, const_cast<void *>(st.first)));
+    tinfo->init_instance(inst_raw_ptr, static_cast<const void *>(&smhldr));
+
+    if (policy == return_value_policy::reference_internal) {
+        keep_alive_impl(inst, parent);
+    }
+
+    return inst.release();
+}
+
+template <typename T>
+handle smart_holder_from_shared_ptr(const std::shared_ptr<T const> &src,
+                                    return_value_policy policy,
+                                    handle parent,
+                                    const std::pair<const void *, const type_info *> &st) {
+    return smart_holder_from_shared_ptr(std::const_pointer_cast<T>(src), // Const2Mutbl
+                                        policy,
+                                        parent,
+                                        st);
+}
+
+struct shared_ptr_parent_life_support {
+    PyObject *parent;
+    explicit shared_ptr_parent_life_support(PyObject *parent) : parent{parent} {
+        Py_INCREF(parent);
+    }
+    // NOLINTNEXTLINE(readability-make-member-function-const)
+    void operator()(void *) {
+        gil_scoped_acquire gil;
+        Py_DECREF(parent);
+    }
+};
+
+struct shared_ptr_trampoline_self_life_support {
+    PyObject *self;
+    explicit shared_ptr_trampoline_self_life_support(instance *inst)
+        : self{reinterpret_cast<PyObject *>(inst)} {
+        gil_scoped_acquire gil;
+        Py_INCREF(self);
+    }
+    // NOLINTNEXTLINE(readability-make-member-function-const)
+    void operator()(void *) {
+        gil_scoped_acquire gil;
+        Py_DECREF(self);
+    }
+};
+
+template <typename T,
+          typename D,
+          typename std::enable_if<std::is_default_constructible<D>::value, int>::type = 0>
+inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) {
+    if (deleter == nullptr) {
+        return std::unique_ptr<T, D>(raw_ptr);
+    }
+    return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter));
+}
+
+template <typename T,
+          typename D,
+          typename std::enable_if<!std::is_default_constructible<D>::value, int>::type = 0>
+inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) {
+    if (deleter == nullptr) {
+        pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no"
+                      " instance available to return.");
+    }
+    return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter));
+}
+
+template <typename T>
+struct load_helper : value_and_holder_helper {
+    bool was_populated = false;
+    bool python_instance_is_alias = false;
+
+    void maybe_set_python_instance_is_alias(handle src) {
+        if (was_populated) {
+            python_instance_is_alias = reinterpret_cast<instance *>(src.ptr())->is_alias;
+        }
+    }
+
+    static std::shared_ptr<T> make_shared_ptr_with_responsible_parent(T *raw_ptr, handle parent) {
+        return std::shared_ptr<T>(raw_ptr, shared_ptr_parent_life_support(parent.ptr()));
+    }
+
+    std::shared_ptr<T> load_as_shared_ptr(void *void_raw_ptr,
+                                          handle responsible_parent = nullptr) const {
+        if (!have_holder()) {
+            return nullptr;
+        }
+        throw_if_uninitialized_or_disowned_holder(typeid(T));
+        smart_holder &hld = holder();
+        hld.ensure_is_not_disowned("load_as_shared_ptr");
+        if (hld.vptr_is_using_noop_deleter) {
+            if (responsible_parent) {
+                return make_shared_ptr_with_responsible_parent(static_cast<T *>(void_raw_ptr),
+                                                               responsible_parent);
+            }
+            throw std::runtime_error("Non-owning holder (load_as_shared_ptr).");
+        }
+        auto *type_raw_ptr = static_cast<T *>(void_raw_ptr);
+        if (python_instance_is_alias) {
+            auto *vptr_gd_ptr = std::get_deleter<pybindit::memory::guarded_delete>(hld.vptr);
+            if (vptr_gd_ptr != nullptr) {
+                std::shared_ptr<void> released_ptr = vptr_gd_ptr->released_ptr.lock();
+                if (released_ptr) {
+                    return std::shared_ptr<T>(released_ptr, type_raw_ptr);
+                }
+                std::shared_ptr<T> to_be_released(
+                    type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst));
+                vptr_gd_ptr->released_ptr = to_be_released;
+                return to_be_released;
+            }
+            auto *sptsls_ptr = std::get_deleter<shared_ptr_trampoline_self_life_support>(hld.vptr);
+            if (sptsls_ptr != nullptr) {
+                // This code is reachable only if there are multiple registered_instances for the
+                // same pointee.
+                if (reinterpret_cast<PyObject *>(loaded_v_h.inst) == sptsls_ptr->self) {
+                    pybind11_fail("smart_holder_type_caster_support load_as_shared_ptr failure: "
+                                  "loaded_v_h.inst == sptsls_ptr->self");
+                }
+            }
+            if (sptsls_ptr != nullptr
+                || !pybindit::memory::type_has_shared_from_this(type_raw_ptr)) {
+                return std::shared_ptr<T>(
+                    type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst));
+            }
+            if (hld.vptr_is_external_shared_ptr) {
+                pybind11_fail("smart_holder_type_casters load_as_shared_ptr failure: not "
+                              "implemented: trampoline-self-life-support for external shared_ptr "
+                              "to type inheriting from std::enable_shared_from_this.");
+            }
+            pybind11_fail(
+                "smart_holder_type_casters: load_as_shared_ptr failure: internal inconsistency.");
+        }
+        std::shared_ptr<void> void_shd_ptr = hld.template as_shared_ptr<void>();
+        return std::shared_ptr<T>(void_shd_ptr, type_raw_ptr);
+    }
+
+    template <typename D>
+    std::unique_ptr<T, D> load_as_unique_ptr(void *raw_void_ptr,
+                                             const char *context = "load_as_unique_ptr") {
+        if (!have_holder()) {
+            return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
+        }
+        throw_if_uninitialized_or_disowned_holder(typeid(T));
+        throw_if_instance_is_currently_owned_by_shared_ptr();
+        holder().ensure_is_not_disowned(context);
+        holder().template ensure_compatible_rtti_uqp_del<T, D>(context);
+        holder().ensure_use_count_1(context);
+
+        T *raw_type_ptr = static_cast<T *>(raw_void_ptr);
+
+        auto *self_life_support
+            = dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(raw_type_ptr);
+        if (self_life_support == nullptr && python_instance_is_alias) {
+            throw value_error("Alias class (also known as trampoline) does not inherit from "
+                              "py::trampoline_self_life_support, therefore the ownership of this "
+                              "instance cannot safely be transferred to C++.");
+        }
+
+        std::unique_ptr<D> extracted_deleter = holder().template extract_deleter<T, D>(context);
+
+        // Critical transfer-of-ownership section. This must stay together.
+        if (self_life_support != nullptr) {
+            holder().disown();
+        } else {
+            holder().release_ownership();
+        }
+        auto result = unique_with_deleter<T, D>(raw_type_ptr, std::move(extracted_deleter));
+        if (self_life_support != nullptr) {
+            self_life_support->activate_life_support(loaded_v_h);
+        } else {
+            void *value_void_ptr = loaded_v_h.value_ptr();
+            loaded_v_h.value_ptr() = nullptr;
+            deregister_instance(loaded_v_h.inst, value_void_ptr, loaded_v_h.type);
+        }
+        // Critical section end.
+
+        return result;
+    }
+
+    // This assumes load_as_shared_ptr succeeded(), and the returned shared_ptr is still alive.
+    // The returned unique_ptr is meant to never expire (the behavior is undefined otherwise).
+    template <typename D>
+    std::unique_ptr<T, D>
+    load_as_const_unique_ptr(T *raw_type_ptr, const char *context = "load_as_const_unique_ptr") {
+        if (!have_holder()) {
+            return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
+        }
+        holder().template ensure_compatible_rtti_uqp_del<T, D>(context);
+        return unique_with_deleter<T, D>(
+            raw_type_ptr, std::move(holder().template extract_deleter<T, D>(context)));
+    }
+};
+
+PYBIND11_NAMESPACE_END(smart_holder_type_caster_support)
+
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+
 class type_caster_generic {
 public:
     PYBIND11_NOINLINE explicit type_caster_generic(const std::type_info &type_info)
@@ -611,6 +974,17 @@
 
     // Base methods for generic caster; there are overridden in copyable_holder_caster
     void load_value(value_and_holder &&v_h) {
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+        if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
+            smart_holder_type_caster_support::value_and_holder_helper v_h_helper;
+            v_h_helper.loaded_v_h = v_h;
+            if (v_h_helper.have_holder()) {
+                v_h_helper.throw_if_uninitialized_or_disowned_holder(cpptype->name());
+                value = v_h_helper.holder().template as_raw_ptr_unowned<void>();
+                return;
+            }
+        }
+#endif
         auto *&vptr = v_h.value_ptr();
         // Lazy allocation for unallocated values:
         if (vptr == nullptr) {
diff --git a/include/pybind11/detail/using_smart_holder.h b/include/pybind11/detail/using_smart_holder.h
new file mode 100644
index 0000000..b2d2606
--- /dev/null
+++ b/include/pybind11/detail/using_smart_holder.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2024 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+#include "common.h"
+#include "internals.h"
+
+#include <type_traits>
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+#    include "struct_smart_holder.h"
+#endif
+
+PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+using pybindit::memory::smart_holder;
+#endif
+
+PYBIND11_NAMESPACE_BEGIN(detail)
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+template <typename H>
+using is_smart_holder = std::is_same<H, smart_holder>;
+#else
+template <typename>
+struct is_smart_holder : std::false_type {};
+#endif
+
+PYBIND11_NAMESPACE_END(detail)
+PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 8a1739e..a09aafe 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -10,8 +10,10 @@
 
 #pragma once
 #include "detail/class.h"
+#include "detail/dynamic_raw_ptr_cast_if_possible.h"
 #include "detail/exception_translation.h"
 #include "detail/init.h"
+#include "detail/using_smart_holder.h"
 #include "attr.h"
 #include "gil.h"
 #include "gil_safe_call_once.h"
@@ -1446,6 +1448,9 @@
         tinfo->simple_ancestors = true;
         tinfo->default_holder = rec.default_holder;
         tinfo->module_local = rec.module_local;
+#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT
+        tinfo->holder_enum_v = rec.holder_enum_v;
+#endif
 
         with_internals([&](internals &internals) {
             auto tindex = std::type_index(*rec.type);
@@ -1618,6 +1623,245 @@
     return pmf;
 }
 
+PYBIND11_NAMESPACE_BEGIN(detail)
+
+// Helper for the property_cpp_function static member functions below.
+// The only purpose of these functions is to support .def_readonly & .def_readwrite.
+// In this context, the PM template parameter is certain to be a Pointer to a Member.
+// The main purpose of must_be_member_function_pointer is to make this obvious, and to guard
+// against accidents. As a side-effect, it also explains why the syntactical overhead for
+// perfect forwarding is not needed.
+template <typename PM>
+using must_be_member_function_pointer = enable_if_t<std::is_member_pointer<PM>::value, int>;
+
+// Note that property_cpp_function is intentionally in the main pybind11 namespace,
+// because user-defined specializations could be useful.
+
+// Classic (non-smart_holder) implementations for .def_readonly and .def_readwrite
+// getter and setter functions.
+// WARNING: This classic implementation can lead to dangling pointers for raw pointer members.
+// See test_ptr() in tests/test_class_sh_property.py
+// However, this implementation works as-is (and safely) for smart_holder std::shared_ptr members.
+template <typename T, typename D>
+struct property_cpp_function_classic {
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function readonly(PM pm, const handle &hdl) {
+        return cpp_function([pm](const T &c) -> const D & { return c.*pm; }, is_method(hdl));
+    }
+
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function read(PM pm, const handle &hdl) {
+        return readonly(pm, hdl);
+    }
+
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function write(PM pm, const handle &hdl) {
+        return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl));
+    }
+};
+
+PYBIND11_NAMESPACE_END(detail)
+
+template <typename T, typename D, typename SFINAE = void>
+struct property_cpp_function : detail::property_cpp_function_classic<T, D> {};
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+PYBIND11_NAMESPACE_BEGIN(detail)
+
+template <typename T, typename D, typename SFINAE = void>
+struct both_t_and_d_use_type_caster_base : std::false_type {};
+
+// `T` is assumed to be equivalent to `intrinsic_t<T>`.
+// `D` is may or may not be equivalent to `intrinsic_t<D>`.
+template <typename T, typename D>
+struct both_t_and_d_use_type_caster_base<
+    T,
+    D,
+    enable_if_t<all_of<std::is_base_of<type_caster_base<T>, type_caster<T>>,
+                       std::is_base_of<type_caster_base<intrinsic_t<D>>, make_caster<D>>>::value>>
+    : std::true_type {};
+
+// Specialization for raw pointer members, using smart_holder if that is the class_ holder,
+// or falling back to the classic implementation if not.
+// WARNING: Like the classic implementation, this implementation can lead to dangling pointers.
+// See test_ptr() in tests/test_class_sh_property.py
+// However, the read functions return a shared_ptr to the member, emulating the PyCLIF approach:
+// https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233
+// This prevents disowning of the Python object owning the raw pointer member.
+template <typename T, typename D>
+struct property_cpp_function_sh_raw_ptr_member {
+    using drp = typename std::remove_pointer<D>::type;
+
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function readonly(PM pm, const handle &hdl) {
+        type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
+        if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
+            return cpp_function(
+                [pm](handle c_hdl) -> std::shared_ptr<drp> {
+                    std::shared_ptr<T> c_sp
+                        = type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
+                            c_hdl);
+                    D ptr = (*c_sp).*pm;
+                    return std::shared_ptr<drp>(c_sp, ptr);
+                },
+                is_method(hdl));
+        }
+        return property_cpp_function_classic<T, D>::readonly(pm, hdl);
+    }
+
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function read(PM pm, const handle &hdl) {
+        return readonly(pm, hdl);
+    }
+
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function write(PM pm, const handle &hdl) {
+        type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
+        if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
+            return cpp_function([pm](T &c, D value) { c.*pm = std::forward<D>(std::move(value)); },
+                                is_method(hdl));
+        }
+        return property_cpp_function_classic<T, D>::write(pm, hdl);
+    }
+};
+
+// Specialization for members held by-value, using smart_holder if that is the class_ holder,
+// or falling back to the classic implementation if not.
+// The read functions return a shared_ptr to the member, emulating the PyCLIF approach:
+// https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233
+// This prevents disowning of the Python object owning the member.
+template <typename T, typename D>
+struct property_cpp_function_sh_member_held_by_value {
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function readonly(PM pm, const handle &hdl) {
+        type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
+        if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
+            return cpp_function(
+                [pm](handle c_hdl) -> std::shared_ptr<typename std::add_const<D>::type> {
+                    std::shared_ptr<T> c_sp
+                        = type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
+                            c_hdl);
+                    return std::shared_ptr<typename std::add_const<D>::type>(c_sp,
+                                                                             &(c_sp.get()->*pm));
+                },
+                is_method(hdl));
+        }
+        return property_cpp_function_classic<T, D>::readonly(pm, hdl);
+    }
+
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function read(PM pm, const handle &hdl) {
+        type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
+        if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
+            return cpp_function(
+                [pm](handle c_hdl) -> std::shared_ptr<D> {
+                    std::shared_ptr<T> c_sp
+                        = type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
+                            c_hdl);
+                    return std::shared_ptr<D>(c_sp, &(c_sp.get()->*pm));
+                },
+                is_method(hdl));
+        }
+        return property_cpp_function_classic<T, D>::read(pm, hdl);
+    }
+
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function write(PM pm, const handle &hdl) {
+        type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
+        if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
+            return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl));
+        }
+        return property_cpp_function_classic<T, D>::write(pm, hdl);
+    }
+};
+
+// Specialization for std::unique_ptr members, using smart_holder if that is the class_ holder,
+// or falling back to the classic implementation if not.
+// read disowns the member unique_ptr.
+// write disowns the passed Python object.
+// readonly is disabled (static_assert) because there is no safe & intuitive way to make the member
+// accessible as a Python object without disowning the member unique_ptr. A .def_readonly disowning
+// the unique_ptr member is deemed highly prone to misunderstandings.
+template <typename T, typename D>
+struct property_cpp_function_sh_unique_ptr_member {
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function readonly(PM, const handle &) {
+        static_assert(!is_instantiation<std::unique_ptr, D>::value,
+                      "def_readonly cannot be used for std::unique_ptr members.");
+        return cpp_function{}; // Unreachable.
+    }
+
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function read(PM pm, const handle &hdl) {
+        type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
+        if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
+            return cpp_function(
+                [pm](handle c_hdl) -> D {
+                    std::shared_ptr<T> c_sp
+                        = type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
+                            c_hdl);
+                    return D{std::move(c_sp.get()->*pm)};
+                },
+                is_method(hdl));
+        }
+        return property_cpp_function_classic<T, D>::read(pm, hdl);
+    }
+
+    template <typename PM, must_be_member_function_pointer<PM> = 0>
+    static cpp_function write(PM pm, const handle &hdl) {
+        return cpp_function([pm](T &c, D &&value) { c.*pm = std::move(value); }, is_method(hdl));
+    }
+};
+
+PYBIND11_NAMESPACE_END(detail)
+
+template <typename T, typename D>
+struct property_cpp_function<
+    T,
+    D,
+    detail::enable_if_t<detail::all_of<std::is_pointer<D>,
+                                       detail::both_t_and_d_use_type_caster_base<T, D>>::value>>
+    : detail::property_cpp_function_sh_raw_ptr_member<T, D> {};
+
+template <typename T, typename D>
+struct property_cpp_function<T,
+                             D,
+                             detail::enable_if_t<detail::all_of<
+                                 detail::none_of<std::is_pointer<D>,
+                                                 std::is_array<D>,
+                                                 detail::is_instantiation<std::unique_ptr, D>,
+                                                 detail::is_instantiation<std::shared_ptr, D>>,
+                                 detail::both_t_and_d_use_type_caster_base<T, D>>::value>>
+    : detail::property_cpp_function_sh_member_held_by_value<T, D> {};
+
+template <typename T, typename D>
+struct property_cpp_function<
+    T,
+    D,
+    detail::enable_if_t<detail::all_of<
+        detail::is_instantiation<std::unique_ptr, D>,
+        detail::both_t_and_d_use_type_caster_base<T, typename D::element_type>>::value>>
+    : detail::property_cpp_function_sh_unique_ptr_member<T, D> {};
+
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+
+#if defined(PYBIND11_USE_SMART_HOLDER_AS_DEFAULT) && defined(PYBIND11_SMART_HOLDER_ENABLED)
+// NOTE: THIS IS MEANT FOR STRESS-TESTING ONLY!
+//       As of PR #5257, for production use, there is no longer a strong reason to make
+//       smart_holder the default holder:
+//           Simply use `py::classh` (see below) instead of `py::class_` as needed.
+//       Running the pybind11 unit tests with smart_holder as the default holder is to ensure
+//       that `py::smart_holder` / `py::classh` is backward-compatible with all pre-existing
+//       functionality.
+#    define PYBIND11_ACTUALLY_USING_SMART_HOLDER_AS_DEFAULT
+template <typename>
+using default_holder_type = smart_holder;
+#else
+template <typename T>
+using default_holder_type = std::unique_ptr<T>;
+#endif
+
 template <typename type_, typename... options>
 class class_ : public detail::generic_type {
     template <typename T>
@@ -1634,7 +1878,7 @@
     using type = type_;
     using type_alias = detail::exactly_one_t<is_subtype, void, options...>;
     constexpr static bool has_alias = !std::is_void<type_alias>::value;
-    using holder_type = detail::exactly_one_t<is_holder, std::unique_ptr<type>, options...>;
+    using holder_type = detail::exactly_one_t<is_holder, default_holder_type<type>, options...>;
 
     static_assert(detail::all_of<is_valid_class_option<options>...>::value,
                   "Unknown/invalid class_ template parameters provided");
@@ -1665,8 +1909,22 @@
         record.type_align = alignof(conditional_t<has_alias, type_alias, type> &);
         record.holder_size = sizeof(holder_type);
         record.init_instance = init_instance;
+
+        // A more fitting name would be uses_unique_ptr_holder.
         record.default_holder = detail::is_instantiation<std::unique_ptr, holder_type>::value;
 
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+        if (detail::is_instantiation<std::unique_ptr, holder_type>::value) {
+            record.holder_enum_v = detail::holder_enum_t::std_unique_ptr;
+        } else if (detail::is_instantiation<std::shared_ptr, holder_type>::value) {
+            record.holder_enum_v = detail::holder_enum_t::std_shared_ptr;
+        } else if (std::is_same<holder_type, smart_holder>::value) {
+            record.holder_enum_v = detail::holder_enum_t::smart_holder;
+        } else {
+            record.holder_enum_v = detail::holder_enum_t::custom_holder;
+        }
+#endif
+
         set_operator_new<type>(&record);
 
         /* Register base classes specified via template arguments to class_, if any */
@@ -1804,9 +2062,11 @@
     class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
         static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
                       "def_readwrite() requires a class member (or base class member)");
-        cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this)),
-            fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this));
-        def_property(name, fget, fset, return_value_policy::reference_internal, extra...);
+        def_property(name,
+                     property_cpp_function<type, D>::read(pm, *this),
+                     property_cpp_function<type, D>::write(pm, *this),
+                     return_value_policy::reference_internal,
+                     extra...);
         return *this;
     }
 
@@ -1814,8 +2074,10 @@
     class_ &def_readonly(const char *name, const D C::*pm, const Extra &...extra) {
         static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
                       "def_readonly() requires a class member (or base class member)");
-        cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this));
-        def_property_readonly(name, fget, return_value_policy::reference_internal, extra...);
+        def_property_readonly(name,
+                              property_cpp_function<type, D>::readonly(pm, *this),
+                              return_value_policy::reference_internal,
+                              extra...);
         return *this;
     }
 
@@ -1992,6 +2254,8 @@
     /// instance.  Should be called as soon as the `type` value_ptr is set for an instance.  Takes
     /// an optional pointer to an existing holder to use; if not specified and the instance is
     /// `.owned`, a new holder will be constructed to manage the value pointer.
+    template <typename H = holder_type,
+              detail::enable_if_t<!detail::is_smart_holder<H>::value, int> = 0>
     static void init_instance(detail::instance *inst, const void *holder_ptr) {
         auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type)));
         if (!v_h.instance_registered()) {
@@ -2001,6 +2265,69 @@
         init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr<type>());
     }
 
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+    template <typename WrappedType>
+    static bool try_initialization_using_shared_from_this(holder_type *, WrappedType *, ...) {
+        return false;
+    }
+
+    // Adopting existing approach used by type_caster_base, although it leads to somewhat fuzzy
+    // ownership semantics: if we detected via shared_from_this that a shared_ptr exists already,
+    // it is reused, irrespective of the return_value_policy in effect.
+    // "SomeBaseOfWrappedType" is needed because std::enable_shared_from_this is not necessarily a
+    // direct base of WrappedType.
+    template <typename WrappedType, typename SomeBaseOfWrappedType>
+    static bool try_initialization_using_shared_from_this(
+        holder_type *uninitialized_location,
+        WrappedType *value_ptr_w_t,
+        const std::enable_shared_from_this<SomeBaseOfWrappedType> *) {
+        auto shd_ptr = std::dynamic_pointer_cast<WrappedType>(
+            detail::try_get_shared_from_this(value_ptr_w_t));
+        if (!shd_ptr) {
+            return false;
+        }
+        // Note: inst->owned ignored.
+        new (uninitialized_location) holder_type(holder_type::from_shared_ptr(shd_ptr));
+        return true;
+    }
+
+    template <typename H = holder_type,
+              detail::enable_if_t<detail::is_smart_holder<H>::value, int> = 0>
+    static void init_instance(detail::instance *inst, const void *holder_const_void_ptr) {
+        // Need for const_cast is a consequence of the type_info::init_instance type:
+        // void (*init_instance)(instance *, const void *);
+        auto *holder_void_ptr = const_cast<void *>(holder_const_void_ptr);
+
+        auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type)));
+        if (!v_h.instance_registered()) {
+            register_instance(inst, v_h.value_ptr(), v_h.type);
+            v_h.set_instance_registered();
+        }
+        auto *uninitialized_location = std::addressof(v_h.holder<holder_type>());
+        auto *value_ptr_w_t = v_h.value_ptr<type>();
+        // Try downcast from `type` to `type_alias`:
+        inst->is_alias
+            = detail::dynamic_raw_ptr_cast_if_possible<type_alias>(value_ptr_w_t) != nullptr;
+        if (holder_void_ptr) {
+            // Note: inst->owned ignored.
+            auto *holder_ptr = static_cast<holder_type *>(holder_void_ptr);
+            new (uninitialized_location) holder_type(std::move(*holder_ptr));
+        } else if (!try_initialization_using_shared_from_this(
+                       uninitialized_location, value_ptr_w_t, value_ptr_w_t)) {
+            if (inst->owned) {
+                new (uninitialized_location) holder_type(holder_type::from_raw_ptr_take_ownership(
+                    value_ptr_w_t, /*void_cast_raw_ptr*/ inst->is_alias));
+            } else {
+                new (uninitialized_location)
+                    holder_type(holder_type::from_raw_ptr_unowned(value_ptr_w_t));
+            }
+        }
+        v_h.set_holder_constructed();
+    }
+
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+
     // Deallocates an instance; via holder, if constructed; otherwise via operator delete.
     // NOTE: The Python error indicator needs to cleared BEFORE this function is called.
     // This is because we could be deallocating while cleaning up after a Python exception.
@@ -2066,6 +2393,18 @@
     }
 };
 
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+// Supports easier switching between py::class_<T> and py::class_<T, py::smart_holder>:
+// users can simply replace the `_` in `class_` with `h` or vice versa.
+template <typename type_, typename... options>
+class classh : public class_<type_, smart_holder, options...> {
+public:
+    using class_<type_, smart_holder, options...>::class_;
+};
+
+#endif
+
 /// Binds an existing constructor taking arguments Args...
 template <typename... Args>
 detail::initimpl::constructor<Args...> init() {
diff --git a/include/pybind11/smart_holder.h b/include/pybind11/smart_holder.h
new file mode 100644
index 0000000..5f568a5
--- /dev/null
+++ b/include/pybind11/smart_holder.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2021-2024 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+#include "pybind11.h"
+
+// Legacy macros introduced with smart_holder_type_casters implementation in 2021.
+// Deprecated.
+#define PYBIND11_TYPE_CASTER_BASE_HOLDER(...)
+#define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...)
+#define PYBIND11_SH_AVL(...) // "Smart_Holder if AVaiLable"
+#define PYBIND11_SH_DEF(...) // "Smart_Holder if DEFault"
diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h
index af3a47f..3eb1e53 100644
--- a/include/pybind11/stl_bind.h
+++ b/include/pybind11/stl_bind.h
@@ -487,7 +487,7 @@
 //
 // std::vector
 //
-template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args>
+template <typename Vector, typename holder_type = default_holder_type<Vector>, typename... Args>
 class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, Args &&...args) {
     using Class_ = class_<Vector, holder_type>;
 
@@ -730,7 +730,7 @@
 
 PYBIND11_NAMESPACE_END(detail)
 
-template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args>
+template <typename Map, typename holder_type = default_holder_type<Map>, typename... Args>
 class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args &&...args) {
     using KeyType = typename Map::key_type;
     using MappedType = typename Map::mapped_type;
diff --git a/include/pybind11/trampoline_self_life_support.h b/include/pybind11/trampoline_self_life_support.h
new file mode 100644
index 0000000..d988e75
--- /dev/null
+++ b/include/pybind11/trampoline_self_life_support.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2021 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+#include "detail/internals.h"
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+#    include "detail/common.h"
+#    include "detail/using_smart_holder.h"
+#    include "detail/value_and_holder.h"
+
+PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+
+PYBIND11_NAMESPACE_BEGIN(detail)
+// PYBIND11:REMINDER: Needs refactoring of existing pybind11 code.
+inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo);
+PYBIND11_NAMESPACE_END(detail)
+
+// The original core idea for this struct goes back to PyCLIF:
+// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37
+// URL provided here mainly to give proper credit.
+struct trampoline_self_life_support {
+    detail::value_and_holder v_h;
+
+    trampoline_self_life_support() = default;
+
+    void activate_life_support(const detail::value_and_holder &v_h_) {
+        Py_INCREF((PyObject *) v_h_.inst);
+        v_h = v_h_;
+    }
+
+    void deactivate_life_support() {
+        Py_DECREF((PyObject *) v_h.inst);
+        v_h = detail::value_and_holder();
+    }
+
+    ~trampoline_self_life_support() {
+        if (v_h.inst != nullptr && v_h.vh != nullptr) {
+            void *value_void_ptr = v_h.value_ptr();
+            if (value_void_ptr != nullptr) {
+                PyGILState_STATE threadstate = PyGILState_Ensure();
+                v_h.value_ptr() = nullptr;
+                v_h.holder<smart_holder>().release_disowned();
+                detail::deregister_instance(v_h.inst, value_void_ptr, v_h.type);
+                Py_DECREF((PyObject *) v_h.inst); // Must be after deregister.
+                PyGILState_Release(threadstate);
+            }
+        }
+    }
+
+    // For the next two, the default implementations generate undefined behavior (ASAN failures
+    // manually verified). The reason is that v_h needs to be kept default-initialized.
+    trampoline_self_life_support(const trampoline_self_life_support &) {}
+    trampoline_self_life_support(trampoline_self_life_support &&) noexcept {}
+
+    // These should never be needed (please provide test cases if you think they are).
+    trampoline_self_life_support &operator=(const trampoline_self_life_support &) = delete;
+    trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete;
+};
+
+PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
+
+#endif // PYBIND11_SMART_HOLDER_ENABLED
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index da396f0..e2fab9c 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -116,6 +116,23 @@
     test_chrono
     test_class
     test_class_release_gil_before_calling_cpp_dtor
+    test_class_sh_basic
+    test_class_sh_disowning
+    test_class_sh_disowning_mi
+    test_class_sh_factory_constructors
+    test_class_sh_inheritance
+    test_class_sh_mi_thunks
+    test_class_sh_property
+    test_class_sh_property_non_owning
+    test_class_sh_shared_ptr_copy_move
+    test_class_sh_trampoline_basic
+    test_class_sh_trampoline_self_life_support
+    test_class_sh_trampoline_shared_from_this
+    test_class_sh_trampoline_shared_ptr_cpp_arg
+    test_class_sh_trampoline_unique_ptr
+    test_class_sh_unique_ptr_custom_deleter
+    test_class_sh_unique_ptr_member
+    test_class_sh_virtual_py_cpp_mix
     test_const_name
     test_constants_and_functions
     test_copy_move
@@ -589,6 +606,9 @@
     ${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)
 
 if(NOT PYBIND11_CUDA_TESTS)
+  # Test pure C++ code (not depending on Python). Provides the `test_pure_cpp` target.
+  add_subdirectory(pure_cpp)
+
   # Test embedding the interpreter. Provides the `cpptest` target.
   add_subdirectory(test_embed)
 
diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py
index 977db75..f50fb15 100644
--- a/tests/extra_python_package/test_files.py
+++ b/tests/extra_python_package/test_files.py
@@ -43,8 +43,10 @@
     "include/pybind11/options.h",
     "include/pybind11/pybind11.h",
     "include/pybind11/pytypes.h",
+    "include/pybind11/smart_holder.h",
     "include/pybind11/stl.h",
     "include/pybind11/stl_bind.h",
+    "include/pybind11/trampoline_self_life_support.h",
     "include/pybind11/type_caster_pyobject_ptr.h",
     "include/pybind11/typing.h",
     "include/pybind11/warnings.h",
@@ -62,10 +64,13 @@
     "include/pybind11/detail/common.h",
     "include/pybind11/detail/cpp_conduit.h",
     "include/pybind11/detail/descr.h",
+    "include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h",
     "include/pybind11/detail/init.h",
     "include/pybind11/detail/internals.h",
+    "include/pybind11/detail/struct_smart_holder.h",
     "include/pybind11/detail/type_caster_base.h",
     "include/pybind11/detail/typeid.h",
+    "include/pybind11/detail/using_smart_holder.h",
     "include/pybind11/detail/value_and_holder.h",
     "include/pybind11/detail/exception_translation.h",
 }
@@ -127,6 +132,7 @@
     "LICENSE",
     "MANIFEST.in",
     "README.rst",
+    "README_smart_holder.rst",
     "PKG-INFO",
     "SECURITY.md",
 }
diff --git a/tests/pure_cpp/CMakeLists.txt b/tests/pure_cpp/CMakeLists.txt
new file mode 100644
index 0000000..17be74b
--- /dev/null
+++ b/tests/pure_cpp/CMakeLists.txt
@@ -0,0 +1,20 @@
+find_package(Catch 2.13.2)
+
+if(CATCH_FOUND)
+  message(STATUS "Building pure C++ tests (not depending on Python) using Catch v${CATCH_VERSION}")
+else()
+  message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers"
+                 " manually or use `cmake -DDOWNLOAD_CATCH=ON` to fetch them automatically.")
+  return()
+endif()
+
+add_executable(smart_holder_poc_test smart_holder_poc_test.cpp)
+pybind11_enable_warnings(smart_holder_poc_test)
+target_link_libraries(smart_holder_poc_test PRIVATE pybind11::headers Catch2::Catch2)
+
+add_custom_target(
+  test_pure_cpp
+  COMMAND "$<TARGET_FILE:smart_holder_poc_test>"
+  WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
+
+add_dependencies(check test_pure_cpp)
diff --git a/tests/pure_cpp/smart_holder_poc.h b/tests/pure_cpp/smart_holder_poc.h
new file mode 100644
index 0000000..320311b
--- /dev/null
+++ b/tests/pure_cpp/smart_holder_poc.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2020-2024 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#pragma once
+
+#include "pybind11/detail/struct_smart_holder.h"
+
+namespace pybindit {
+namespace memory {
+namespace smart_holder_poc { // Proof-of-Concept implementations.
+
+template <typename T>
+T &as_lvalue_ref(const smart_holder &hld) {
+    static const char *context = "as_lvalue_ref";
+    hld.ensure_is_populated(context);
+    hld.ensure_has_pointee(context);
+    return *hld.as_raw_ptr_unowned<T>();
+}
+
+template <typename T>
+T &&as_rvalue_ref(const smart_holder &hld) {
+    static const char *context = "as_rvalue_ref";
+    hld.ensure_is_populated(context);
+    hld.ensure_has_pointee(context);
+    return std::move(*hld.as_raw_ptr_unowned<T>());
+}
+
+template <typename T>
+T *as_raw_ptr_release_ownership(smart_holder &hld,
+                                const char *context = "as_raw_ptr_release_ownership") {
+    hld.ensure_can_release_ownership(context);
+    T *raw_ptr = hld.as_raw_ptr_unowned<T>();
+    hld.release_ownership();
+    return raw_ptr;
+}
+
+template <typename T, typename D = std::default_delete<T>>
+std::unique_ptr<T, D> as_unique_ptr(smart_holder &hld) {
+    static const char *context = "as_unique_ptr";
+    hld.ensure_compatible_rtti_uqp_del<T, D>(context);
+    hld.ensure_use_count_1(context);
+    T *raw_ptr = hld.as_raw_ptr_unowned<T>();
+    hld.release_ownership();
+    // KNOWN DEFECT (see PR #4850): Does not copy the deleter.
+    return std::unique_ptr<T, D>(raw_ptr);
+}
+
+} // namespace smart_holder_poc
+} // namespace memory
+} // namespace pybindit
diff --git a/tests/pure_cpp/smart_holder_poc_test.cpp b/tests/pure_cpp/smart_holder_poc_test.cpp
new file mode 100644
index 0000000..24ab643
--- /dev/null
+++ b/tests/pure_cpp/smart_holder_poc_test.cpp
@@ -0,0 +1,415 @@
+#include "smart_holder_poc.h"
+
+#include <functional>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+// Catch uses _ internally, which breaks gettext style defines
+#ifdef _
+#    undef _
+#endif
+
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
+
+using pybindit::memory::smart_holder;
+namespace poc = pybindit::memory::smart_holder_poc;
+
+namespace helpers {
+
+struct movable_int {
+    int valu;
+    explicit movable_int(int v) : valu{v} {}
+    movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; }
+};
+
+template <typename T>
+struct functor_builtin_delete {
+    void operator()(T *ptr) { delete ptr; }
+#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 8)                                   \
+    || (defined(__clang_major__) && __clang_major__ == 3 && __clang_minor__ == 6)
+    // Workaround for these errors:
+    // gcc 4.8.5: too many initializers for 'helpers::functor_builtin_delete<int>'
+    // clang 3.6: excess elements in struct initializer
+    functor_builtin_delete() = default;
+    functor_builtin_delete(const functor_builtin_delete &) {}
+    functor_builtin_delete(functor_builtin_delete &&) {}
+#endif
+};
+
+template <typename T>
+struct functor_other_delete : functor_builtin_delete<T> {};
+
+struct indestructible_int {
+    int valu;
+    explicit indestructible_int(int v) : valu{v} {}
+
+private:
+    ~indestructible_int() = default;
+};
+
+struct base {
+    virtual int get() { return 10; }
+    virtual ~base() = default;
+};
+
+struct derived : public base {
+    int get() override { return 100; }
+};
+
+} // namespace helpers
+
+TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") {
+    static int value = 19;
+    auto hld = smart_holder::from_raw_ptr_unowned(&value);
+    REQUIRE(*hld.as_raw_ptr_unowned<int>() == 19);
+}
+
+TEST_CASE("from_raw_ptr_unowned+as_lvalue_ref", "[S]") {
+    static int value = 19;
+    auto hld = smart_holder::from_raw_ptr_unowned(&value);
+    REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
+}
+
+TEST_CASE("from_raw_ptr_unowned+as_rvalue_ref", "[S]") {
+    helpers::movable_int orig(19);
+    {
+        auto hld = smart_holder::from_raw_ptr_unowned(&orig);
+        helpers::movable_int othr(poc::as_rvalue_ref<helpers::movable_int>(hld));
+        REQUIRE(othr.valu == 19);
+        REQUIRE(orig.valu == 91);
+    }
+}
+
+TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_release_ownership", "[E]") {
+    static int value = 19;
+    auto hld = smart_holder::from_raw_ptr_unowned(&value);
+    REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
+                        "Cannot disown non-owning holder (as_raw_ptr_release_ownership).");
+}
+
+TEST_CASE("from_raw_ptr_unowned+as_unique_ptr", "[E]") {
+    static int value = 19;
+    auto hld = smart_holder::from_raw_ptr_unowned(&value);
+    REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
+                        "Cannot disown non-owning holder (as_unique_ptr).");
+}
+
+TEST_CASE("from_raw_ptr_unowned+as_unique_ptr_with_deleter", "[E]") {
+    static int value = 19;
+    auto hld = smart_holder::from_raw_ptr_unowned(&value);
+    REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
+                        "Missing unique_ptr deleter (as_unique_ptr).");
+}
+
+TEST_CASE("from_raw_ptr_unowned+as_shared_ptr", "[S]") {
+    static int value = 19;
+    auto hld = smart_holder::from_raw_ptr_unowned(&value);
+    REQUIRE(*hld.as_shared_ptr<int>() == 19);
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+as_lvalue_ref", "[S]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    REQUIRE(hld.has_pointee());
+    REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership1", "[S]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    auto new_owner = std::unique_ptr<int>(poc::as_raw_ptr_release_ownership<int>(hld));
+    REQUIRE(!hld.has_pointee());
+    REQUIRE(*new_owner == 19);
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership2", "[E]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    auto shd_ptr = hld.as_shared_ptr<int>();
+    REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
+                        "Cannot disown use_count != 1 (as_raw_ptr_release_ownership).");
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr1", "[S]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    std::unique_ptr<int> new_owner = poc::as_unique_ptr<int>(hld);
+    REQUIRE(!hld.has_pointee());
+    REQUIRE(*new_owner == 19);
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr2", "[E]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    auto shd_ptr = hld.as_shared_ptr<int>();
+    REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
+                        "Cannot disown use_count != 1 (as_unique_ptr).");
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr_with_deleter", "[E]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
+                        "Missing unique_ptr deleter (as_unique_ptr).");
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr", "[S]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>();
+    REQUIRE(hld.has_pointee());
+    REQUIRE(*new_owner == 19);
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+disown+reclaim_disowned", "[S]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
+    hld.disown();
+    REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
+    REQUIRE(*new_owner == 19);
+    hld.reclaim_disowned(); // Manually veriified: without this, clang++ -fsanitize=address reports
+                            // "detected memory leaks".
+    // NOLINTNEXTLINE(bugprone-unused-return-value)
+    (void) new_owner.release(); // Manually verified: without this, clang++ -fsanitize=address
+                                // reports "attempting double-free".
+    REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
+    REQUIRE(new_owner.get() == nullptr);
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+disown+release_disowned", "[S]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
+    hld.disown();
+    REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
+    REQUIRE(*new_owner == 19);
+    hld.release_disowned();
+    REQUIRE(!hld.has_pointee());
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+disown+ensure_is_not_disowned", "[E]") {
+    const char *context = "test_case";
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    hld.ensure_is_not_disowned(context); // Does not throw.
+    std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
+    hld.disown();
+    REQUIRE_THROWS_WITH(hld.ensure_is_not_disowned(context),
+                        "Holder was disowned already (test_case).");
+}
+
+TEST_CASE("from_unique_ptr+as_lvalue_ref", "[S]") {
+    std::unique_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
+}
+
+TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership1", "[S]") {
+    std::unique_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    auto new_owner = std::unique_ptr<int>(poc::as_raw_ptr_release_ownership<int>(hld));
+    REQUIRE(!hld.has_pointee());
+    REQUIRE(*new_owner == 19);
+}
+
+TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership2", "[E]") {
+    std::unique_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    auto shd_ptr = hld.as_shared_ptr<int>();
+    REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
+                        "Cannot disown use_count != 1 (as_raw_ptr_release_ownership).");
+}
+
+TEST_CASE("from_unique_ptr+as_unique_ptr1", "[S]") {
+    std::unique_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    std::unique_ptr<int> new_owner = poc::as_unique_ptr<int>(hld);
+    REQUIRE(!hld.has_pointee());
+    REQUIRE(*new_owner == 19);
+}
+
+TEST_CASE("from_unique_ptr+as_unique_ptr2", "[E]") {
+    std::unique_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    auto shd_ptr = hld.as_shared_ptr<int>();
+    REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
+                        "Cannot disown use_count != 1 (as_unique_ptr).");
+}
+
+TEST_CASE("from_unique_ptr+as_unique_ptr_with_deleter", "[E]") {
+    std::unique_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
+                        "Incompatible unique_ptr deleter (as_unique_ptr).");
+}
+
+TEST_CASE("from_unique_ptr+as_shared_ptr", "[S]") {
+    std::unique_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>();
+    REQUIRE(hld.has_pointee());
+    REQUIRE(*new_owner == 19);
+}
+
+TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base", "[S]") {
+    std::unique_ptr<helpers::derived> orig_owner(new helpers::derived());
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    std::unique_ptr<helpers::base> new_owner = poc::as_unique_ptr<helpers::base>(hld);
+    REQUIRE(!hld.has_pointee());
+    REQUIRE(new_owner->get() == 100);
+}
+
+TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base2", "[E]") {
+    std::unique_ptr<helpers::derived, helpers::functor_other_delete<helpers::derived>> orig_owner(
+        new helpers::derived());
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    REQUIRE_THROWS_WITH(
+        (poc::as_unique_ptr<helpers::base, helpers::functor_builtin_delete<helpers::base>>(hld)),
+        "Incompatible unique_ptr deleter (as_unique_ptr).");
+}
+
+TEST_CASE("from_unique_ptr_with_deleter+as_lvalue_ref", "[S]") {
+    std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
+}
+
+TEST_CASE("from_unique_ptr_with_std_function_deleter+as_lvalue_ref", "[S]") {
+    std::unique_ptr<int, std::function<void(const int *)>> orig_owner(
+        new int(19), [](const int *raw_ptr) { delete raw_ptr; });
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
+}
+
+TEST_CASE("from_unique_ptr_with_deleter+as_raw_ptr_release_ownership", "[E]") {
+    std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
+                        "Cannot disown custom deleter (as_raw_ptr_release_ownership).");
+}
+
+TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr", "[E]") {
+    std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
+                        "Incompatible unique_ptr deleter (as_unique_ptr).");
+}
+
+TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter1", "[S]") {
+    std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    std::unique_ptr<int, helpers::functor_builtin_delete<int>> new_owner
+        = poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld);
+    REQUIRE(!hld.has_pointee());
+    REQUIRE(*new_owner == 19);
+}
+
+TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter2", "[E]") {
+    std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_other_delete<int>>(hld)),
+                        "Incompatible unique_ptr deleter (as_unique_ptr).");
+}
+
+TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr", "[S]") {
+    std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
+    auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+    REQUIRE(orig_owner.get() == nullptr);
+    std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>();
+    REQUIRE(hld.has_pointee());
+    REQUIRE(*new_owner == 19);
+}
+
+TEST_CASE("from_shared_ptr+as_lvalue_ref", "[S]") {
+    std::shared_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_shared_ptr(orig_owner);
+    REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
+}
+
+TEST_CASE("from_shared_ptr+as_raw_ptr_release_ownership", "[E]") {
+    std::shared_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_shared_ptr(orig_owner);
+    REQUIRE_THROWS_WITH(poc::as_raw_ptr_release_ownership<int>(hld),
+                        "Cannot disown external shared_ptr (as_raw_ptr_release_ownership).");
+}
+
+TEST_CASE("from_shared_ptr+as_unique_ptr", "[E]") {
+    std::shared_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_shared_ptr(orig_owner);
+    REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld),
+                        "Cannot disown external shared_ptr (as_unique_ptr).");
+}
+
+TEST_CASE("from_shared_ptr+as_unique_ptr_with_deleter", "[E]") {
+    std::shared_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_shared_ptr(orig_owner);
+    REQUIRE_THROWS_WITH((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
+                        "Missing unique_ptr deleter (as_unique_ptr).");
+}
+
+TEST_CASE("from_shared_ptr+as_shared_ptr", "[S]") {
+    std::shared_ptr<int> orig_owner(new int(19));
+    auto hld = smart_holder::from_shared_ptr(orig_owner);
+    REQUIRE(*hld.as_shared_ptr<int>() == 19);
+}
+
+TEST_CASE("error_unpopulated_holder", "[E]") {
+    smart_holder hld;
+    REQUIRE_THROWS_WITH(poc::as_lvalue_ref<int>(hld), "Unpopulated holder (as_lvalue_ref).");
+}
+
+TEST_CASE("error_disowned_holder", "[E]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    poc::as_unique_ptr<int>(hld);
+    REQUIRE_THROWS_WITH(poc::as_lvalue_ref<int>(hld), "Disowned holder (as_lvalue_ref).");
+}
+
+TEST_CASE("error_cannot_disown_nullptr", "[E]") {
+    auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+    poc::as_unique_ptr<int>(hld);
+    REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld), "Cannot disown nullptr (as_unique_ptr).");
+}
+
+TEST_CASE("indestructible_int-from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") {
+    using zombie = helpers::indestructible_int;
+    // Using placement new instead of plain new, to not trigger leak sanitizer errors.
+    static std::aligned_storage<sizeof(zombie), alignof(zombie)>::type memory_block[1];
+    auto *value = new (memory_block) zombie(19);
+    auto hld = smart_holder::from_raw_ptr_unowned(value);
+    REQUIRE(hld.as_raw_ptr_unowned<zombie>()->valu == 19);
+}
+
+TEST_CASE("indestructible_int-from_raw_ptr_take_ownership", "[E]") {
+    helpers::indestructible_int *value = nullptr;
+    REQUIRE_THROWS_WITH(smart_holder::from_raw_ptr_take_ownership(value),
+                        "Pointee is not destructible (from_raw_ptr_take_ownership).");
+}
+
+TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr-outliving_smart_holder", "[S]") {
+    // Exercises guarded_builtin_delete flag_ptr validity past destruction of smart_holder.
+    std::shared_ptr<int> longer_living;
+    {
+        auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
+        longer_living = hld.as_shared_ptr<int>();
+    }
+    REQUIRE(*longer_living == 19);
+}
+
+TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr-outliving_smart_holder", "[S]") {
+    // Exercises guarded_custom_deleter flag_ptr validity past destruction of smart_holder.
+    std::shared_ptr<int> longer_living;
+    {
+        std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
+        auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
+        longer_living = hld.as_shared_ptr<int>();
+    }
+    REQUIRE(*longer_living == 19);
+}
diff --git a/tests/test_class.cpp b/tests/test_class.cpp
index cb84c32..e4c0328 100644
--- a/tests/test_class.cpp
+++ b/tests/test_class.cpp
@@ -105,6 +105,16 @@
         .def_static("__new__",
                     [](const py::object &) { return NoConstructorNew::new_instance(); });
 
+    // test_pass_unique_ptr
+    struct ToBeHeldByUniquePtr {};
+    py::class_<ToBeHeldByUniquePtr, std::unique_ptr<ToBeHeldByUniquePtr>>(m, "ToBeHeldByUniquePtr")
+        .def(py::init<>());
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+    m.def("pass_unique_ptr", [](std::unique_ptr<ToBeHeldByUniquePtr> &&) {});
+#else
+    m.attr("pass_unique_ptr") = py::none();
+#endif
+
     // test_inheritance
     class Pet {
     public:
@@ -227,11 +237,12 @@
     m.def("mismatched_holder_1", []() {
         auto mod = py::module_::import("__main__");
         py::class_<MismatchBase1, std::shared_ptr<MismatchBase1>>(mod, "MismatchBase1");
-        py::class_<MismatchDerived1, MismatchBase1>(mod, "MismatchDerived1");
+        py::class_<MismatchDerived1, std::unique_ptr<MismatchDerived1>, MismatchBase1>(
+            mod, "MismatchDerived1");
     });
     m.def("mismatched_holder_2", []() {
         auto mod = py::module_::import("__main__");
-        py::class_<MismatchBase2>(mod, "MismatchBase2");
+        py::class_<MismatchBase2, std::unique_ptr<MismatchBase2>>(mod, "MismatchBase2");
         py::class_<MismatchDerived2, std::shared_ptr<MismatchDerived2>, MismatchBase2>(
             mod, "MismatchDerived2");
     });
@@ -625,8 +636,10 @@
 CHECK_HOLDER(1, unique);
 CHECK_HOLDER(2, unique);
 CHECK_HOLDER(3, unique);
+#ifndef PYBIND11_ACTUALLY_USING_SMART_HOLDER_AS_DEFAULT
 CHECK_HOLDER(4, unique);
 CHECK_HOLDER(5, unique);
+#endif
 CHECK_HOLDER(6, shared);
 CHECK_HOLDER(7, shared);
 CHECK_HOLDER(8, shared);
diff --git a/tests/test_class.py b/tests/test_class.py
index 01963d0..44a5582 100644
--- a/tests/test_class.py
+++ b/tests/test_class.py
@@ -49,6 +49,18 @@
     assert cstats.alive() == 0
 
 
+def test_pass_unique_ptr():
+    obj = m.ToBeHeldByUniquePtr()
+    if m.pass_unique_ptr is None:
+        pytest.skip("smart_holder not available.")
+    with pytest.raises(RuntimeError) as execinfo:
+        m.pass_unique_ptr(obj)
+    assert str(execinfo.value).startswith(
+        "Passing `std::unique_ptr<T>` from Python to C++ requires `py::classh` (with T = "
+    )
+    assert "ToBeHeldByUniquePtr" in str(execinfo.value)
+
+
 def test_type():
     assert m.check_type(1) == m.DerivedClass1
     with pytest.raises(RuntimeError) as execinfo:
diff --git a/tests/test_class_sh_basic.cpp b/tests/test_class_sh_basic.cpp
new file mode 100644
index 0000000..1d23cd6
--- /dev/null
+++ b/tests/test_class_sh_basic.cpp
@@ -0,0 +1,267 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace pybind11_tests {
+namespace class_sh_basic {
+
+struct atyp { // Short for "any type".
+    std::string mtxt;
+    atyp() : mtxt("DefaultConstructor") {}
+    explicit atyp(const std::string &mtxt_) : mtxt(mtxt_) {}
+    atyp(const atyp &other) { mtxt = other.mtxt + "_CpCtor"; }
+    atyp(atyp &&other) noexcept { mtxt = other.mtxt + "_MvCtor"; }
+};
+
+struct uconsumer { // unique_ptr consumer
+    std::unique_ptr<atyp> held;
+    bool valid() const { return static_cast<bool>(held); }
+
+    void pass_valu(std::unique_ptr<atyp> obj) { held = std::move(obj); }
+    void pass_rref(std::unique_ptr<atyp> &&obj) { held = std::move(obj); }
+    std::unique_ptr<atyp> rtrn_valu() { return std::move(held); }
+    std::unique_ptr<atyp> &rtrn_lref() { return held; }
+    const std::unique_ptr<atyp> &rtrn_cref() const { return held; }
+};
+
+/// Custom deleter that is default constructible.
+struct custom_deleter {
+    std::string trace_txt;
+
+    custom_deleter() = default;
+    explicit custom_deleter(const std::string &trace_txt_) : trace_txt(trace_txt_) {}
+
+    custom_deleter(const custom_deleter &other) { trace_txt = other.trace_txt + "_CpCtor"; }
+
+    custom_deleter &operator=(const custom_deleter &rhs) {
+        trace_txt = rhs.trace_txt + "_CpLhs";
+        return *this;
+    }
+
+    custom_deleter(custom_deleter &&other) noexcept {
+        trace_txt = other.trace_txt + "_MvCtorTo";
+        other.trace_txt += "_MvCtorFrom";
+    }
+
+    custom_deleter &operator=(custom_deleter &&rhs) noexcept {
+        trace_txt = rhs.trace_txt + "_MvLhs";
+        rhs.trace_txt += "_MvRhs";
+        return *this;
+    }
+
+    void operator()(atyp *p) const { std::default_delete<atyp>()(p); }
+    void operator()(const atyp *p) const { std::default_delete<const atyp>()(p); }
+};
+static_assert(std::is_default_constructible<custom_deleter>::value, "");
+
+/// Custom deleter that is not default constructible.
+struct custom_deleter_nd : custom_deleter {
+    custom_deleter_nd() = delete;
+    explicit custom_deleter_nd(const std::string &trace_txt_) : custom_deleter(trace_txt_) {}
+};
+static_assert(!std::is_default_constructible<custom_deleter_nd>::value, "");
+
+// clang-format off
+
+atyp        rtrn_valu() { atyp obj{"rtrn_valu"}; return obj; }
+atyp&&      rtrn_rref() { static atyp obj; obj.mtxt = "rtrn_rref"; return std::move(obj); }
+atyp const& rtrn_cref() { static atyp obj; obj.mtxt = "rtrn_cref"; return obj; }
+atyp&       rtrn_mref() { static atyp obj; obj.mtxt = "rtrn_mref"; return obj; }
+atyp const* rtrn_cptr() { return new atyp{"rtrn_cptr"}; }
+atyp*       rtrn_mptr() { return new atyp{"rtrn_mptr"}; }
+
+std::string pass_valu(atyp obj)        { return "pass_valu:" + obj.mtxt; } // NOLINT
+std::string pass_cref(atyp const& obj) { return "pass_cref:" + obj.mtxt; }
+std::string pass_mref(atyp& obj)       { return "pass_mref:" + obj.mtxt; }
+std::string pass_cptr(atyp const* obj) { return "pass_cptr:" + obj->mtxt; }
+std::string pass_mptr(atyp* obj)       { return "pass_mptr:" + obj->mtxt; }
+
+std::shared_ptr<atyp>       rtrn_shmp() { return std::make_shared<atyp>("rtrn_shmp"); }
+std::shared_ptr<atyp const> rtrn_shcp() { return std::shared_ptr<atyp const>(new atyp{"rtrn_shcp"}); }
+
+std::string pass_shmp(std::shared_ptr<atyp>       obj) { return "pass_shmp:" + obj->mtxt; } // NOLINT
+std::string pass_shcp(std::shared_ptr<atyp const> obj) { return "pass_shcp:" + obj->mtxt; } // NOLINT
+
+std::unique_ptr<atyp>       rtrn_uqmp() { return std::unique_ptr<atyp      >(new atyp{"rtrn_uqmp"}); }
+std::unique_ptr<atyp const> rtrn_uqcp() { return std::unique_ptr<atyp const>(new atyp{"rtrn_uqcp"}); }
+
+std::string pass_uqmp(std::unique_ptr<atyp      > obj) { return "pass_uqmp:" + obj->mtxt; }
+std::string pass_uqcp(std::unique_ptr<atyp const> obj) { return "pass_uqcp:" + obj->mtxt; }
+
+struct sddm : std::default_delete<atyp      > {};
+struct sddc : std::default_delete<atyp const> {};
+
+std::unique_ptr<atyp,       sddm> rtrn_udmp() { return std::unique_ptr<atyp,       sddm>(new atyp{"rtrn_udmp"}); }
+std::unique_ptr<atyp const, sddc> rtrn_udcp() { return std::unique_ptr<atyp const, sddc>(new atyp{"rtrn_udcp"}); }
+
+std::string pass_udmp(std::unique_ptr<atyp,       sddm> obj) { return "pass_udmp:" + obj->mtxt; }
+std::string pass_udcp(std::unique_ptr<atyp const, sddc> obj) { return "pass_udcp:" + obj->mtxt; }
+
+std::unique_ptr<atyp,       custom_deleter> rtrn_udmp_del() { return std::unique_ptr<atyp,       custom_deleter>(new atyp{"rtrn_udmp_del"}, custom_deleter{"udmp_deleter"}); }
+std::unique_ptr<atyp const, custom_deleter> rtrn_udcp_del() { return std::unique_ptr<atyp const, custom_deleter>(new atyp{"rtrn_udcp_del"}, custom_deleter{"udcp_deleter"}); }
+
+std::string pass_udmp_del(std::unique_ptr<atyp,       custom_deleter> obj) { return "pass_udmp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
+std::string pass_udcp_del(std::unique_ptr<atyp const, custom_deleter> obj) { return "pass_udcp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
+
+std::unique_ptr<atyp,       custom_deleter_nd> rtrn_udmp_del_nd() { return std::unique_ptr<atyp,       custom_deleter_nd>(new atyp{"rtrn_udmp_del_nd"}, custom_deleter_nd{"udmp_deleter_nd"}); }
+std::unique_ptr<atyp const, custom_deleter_nd> rtrn_udcp_del_nd() { return std::unique_ptr<atyp const, custom_deleter_nd>(new atyp{"rtrn_udcp_del_nd"}, custom_deleter_nd{"udcp_deleter_nd"}); }
+
+std::string pass_udmp_del_nd(std::unique_ptr<atyp,       custom_deleter_nd> obj) { return "pass_udmp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
+std::string pass_udcp_del_nd(std::unique_ptr<atyp const, custom_deleter_nd> obj) { return "pass_udcp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
+
+// clang-format on
+
+// Helpers for testing.
+std::string get_mtxt(atyp const &obj) { return obj.mtxt; }
+std::ptrdiff_t get_ptr(atyp const &obj) { return reinterpret_cast<std::ptrdiff_t>(&obj); }
+
+std::unique_ptr<atyp> unique_ptr_roundtrip(std::unique_ptr<atyp> obj) { return obj; }
+
+std::string pass_unique_ptr_cref(const std::unique_ptr<atyp> &obj) { return obj->mtxt; }
+
+const std::unique_ptr<atyp> &rtrn_unique_ptr_cref(const std::string &mtxt) {
+    static std::unique_ptr<atyp> obj{new atyp{"static_ctor_arg"}};
+    if (!mtxt.empty()) {
+        obj->mtxt = mtxt;
+    }
+    return obj;
+}
+
+const std::unique_ptr<atyp> &unique_ptr_cref_roundtrip(const std::unique_ptr<atyp> &obj) {
+    return obj;
+}
+
+struct SharedPtrStash {
+    std::vector<std::shared_ptr<const atyp>> stash;
+    void Add(const std::shared_ptr<const atyp> &obj) { stash.push_back(obj); }
+};
+
+class LocalUnusualOpRef : UnusualOpRef {}; // To avoid clashing with `py::class_<UnusualOpRef>`.
+py::object CastUnusualOpRefConstRef(const LocalUnusualOpRef &cref) { return py::cast(cref); }
+py::object CastUnusualOpRefMovable(LocalUnusualOpRef &&mvbl) { return py::cast(std::move(mvbl)); }
+
+} // namespace class_sh_basic
+} // namespace pybind11_tests
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_basic::atyp)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_basic::uconsumer)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_basic::SharedPtrStash)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_basic::LocalUnusualOpRef)
+
+namespace pybind11_tests {
+namespace class_sh_basic {
+
+TEST_SUBMODULE(class_sh_basic, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    namespace py = pybind11;
+
+    py::classh<atyp>(m, "atyp").def(py::init<>()).def(py::init([](const std::string &mtxt) {
+        atyp obj;
+        obj.mtxt = mtxt;
+        return obj;
+    }));
+
+    m.def("rtrn_valu", rtrn_valu);
+    m.def("rtrn_rref", rtrn_rref);
+    m.def("rtrn_cref", rtrn_cref);
+    m.def("rtrn_mref", rtrn_mref);
+    m.def("rtrn_cptr", rtrn_cptr);
+    m.def("rtrn_mptr", rtrn_mptr);
+
+    m.def("pass_valu", pass_valu);
+    m.def("pass_cref", pass_cref);
+    m.def("pass_mref", pass_mref);
+    m.def("pass_cptr", pass_cptr);
+    m.def("pass_mptr", pass_mptr);
+
+    m.def("rtrn_shmp", rtrn_shmp);
+    m.def("rtrn_shcp", rtrn_shcp);
+
+    m.def("pass_shmp", pass_shmp);
+    m.def("pass_shcp", pass_shcp);
+
+    m.def("rtrn_uqmp", rtrn_uqmp);
+    m.def("rtrn_uqcp", rtrn_uqcp);
+
+    m.def("pass_uqmp", pass_uqmp);
+    m.def("pass_uqcp", pass_uqcp);
+
+    m.def("rtrn_udmp", rtrn_udmp);
+    m.def("rtrn_udcp", rtrn_udcp);
+
+    m.def("pass_udmp", pass_udmp);
+    m.def("pass_udcp", pass_udcp);
+
+    m.def("rtrn_udmp_del", rtrn_udmp_del);
+    m.def("rtrn_udcp_del", rtrn_udcp_del);
+
+    m.def("pass_udmp_del", pass_udmp_del);
+    m.def("pass_udcp_del", pass_udcp_del);
+
+    m.def("rtrn_udmp_del_nd", rtrn_udmp_del_nd);
+    m.def("rtrn_udcp_del_nd", rtrn_udcp_del_nd);
+
+    m.def("pass_udmp_del_nd", pass_udmp_del_nd);
+    m.def("pass_udcp_del_nd", pass_udcp_del_nd);
+
+    py::classh<uconsumer>(m, "uconsumer")
+        .def(py::init<>())
+        .def("valid", &uconsumer::valid)
+        .def("pass_valu", &uconsumer::pass_valu)
+        .def("pass_rref", &uconsumer::pass_rref)
+        .def("rtrn_valu", &uconsumer::rtrn_valu)
+        .def("rtrn_lref", &uconsumer::rtrn_lref)
+        .def("rtrn_cref", &uconsumer::rtrn_cref);
+
+    // Helpers for testing.
+    // These require selected functions above to work first, as indicated:
+    m.def("get_mtxt", get_mtxt); // pass_cref
+    m.def("get_ptr", get_ptr);   // pass_cref
+
+    m.def("unique_ptr_roundtrip", unique_ptr_roundtrip); // pass_uqmp, rtrn_uqmp
+
+    m.def("pass_unique_ptr_cref", pass_unique_ptr_cref);
+    m.def("rtrn_unique_ptr_cref", rtrn_unique_ptr_cref);
+    m.def("unique_ptr_cref_roundtrip", unique_ptr_cref_roundtrip);
+
+    py::classh<SharedPtrStash>(m, "SharedPtrStash")
+        .def(py::init<>())
+        .def("Add", &SharedPtrStash::Add, py::arg("obj"));
+
+    m.def("py_type_handle_of_atyp", []() {
+        return py::type::handle_of<atyp>(); // Exercises static_cast in this function.
+    });
+
+    // Checks for type names used as arguments
+    m.def("args_shared_ptr", [](std::shared_ptr<atyp> p) { return p; });
+    m.def("args_shared_ptr_const", [](std::shared_ptr<atyp const> p) { return p; });
+    m.def("args_unique_ptr", [](std::unique_ptr<atyp> p) { return p; });
+    m.def("args_unique_ptr_const", [](std::unique_ptr<atyp const> p) { return p; });
+
+    // Make sure unique_ptr type caster accept automatic_reference return value policy.
+    m.def(
+        "rtrn_uq_automatic_reference",
+        []() { return std::unique_ptr<atyp>(new atyp("rtrn_uq_automatic_reference")); },
+        pybind11::return_value_policy::automatic_reference);
+
+    m.def("pass_shared_ptr_ptr", [](std::shared_ptr<atyp> *) {});
+
+    py::classh<LocalUnusualOpRef>(m, "LocalUnusualOpRef");
+    m.def("CallCastUnusualOpRefConstRef",
+          []() { return CastUnusualOpRefConstRef(LocalUnusualOpRef()); });
+    m.def("CallCastUnusualOpRefMovable",
+          []() { return CastUnusualOpRefMovable(LocalUnusualOpRef()); });
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
+
+} // namespace class_sh_basic
+} // namespace pybind11_tests
diff --git a/tests/test_class_sh_basic.py b/tests/test_class_sh_basic.py
new file mode 100644
index 0000000..74a4e40
--- /dev/null
+++ b/tests/test_class_sh_basic.py
@@ -0,0 +1,249 @@
+# Importing re before pytest after observing a PyPy CI flake when importing pytest first.
+from __future__ import annotations
+
+import re
+
+import pytest
+
+from pybind11_tests import class_sh_basic as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+def test_atyp_constructors():
+    obj = m.atyp()
+    assert obj.__class__.__name__ == "atyp"
+    obj = m.atyp("")
+    assert obj.__class__.__name__ == "atyp"
+    obj = m.atyp("txtm")
+    assert obj.__class__.__name__ == "atyp"
+
+
+@pytest.mark.parametrize(
+    ("rtrn_f", "expected"),
+    [
+        (m.rtrn_valu, "rtrn_valu(_MvCtor)*_MvCtor"),
+        (m.rtrn_rref, "rtrn_rref(_MvCtor)*_MvCtor"),
+        (m.rtrn_cref, "rtrn_cref(_MvCtor)*_CpCtor"),
+        (m.rtrn_mref, "rtrn_mref(_MvCtor)*_CpCtor"),
+        (m.rtrn_cptr, "rtrn_cptr"),
+        (m.rtrn_mptr, "rtrn_mptr"),
+        (m.rtrn_shmp, "rtrn_shmp"),
+        (m.rtrn_shcp, "rtrn_shcp"),
+        (m.rtrn_uqmp, "rtrn_uqmp"),
+        (m.rtrn_uqcp, "rtrn_uqcp"),
+        (m.rtrn_udmp, "rtrn_udmp"),
+        (m.rtrn_udcp, "rtrn_udcp"),
+    ],
+)
+def test_cast(rtrn_f, expected):
+    assert re.match(expected, m.get_mtxt(rtrn_f()))
+
+
+@pytest.mark.parametrize(
+    ("pass_f", "mtxt", "expected"),
+    [
+        (m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"),
+        (m.pass_cref, "Cref", "pass_cref:Cref(_MvCtor)*_MvCtor"),
+        (m.pass_mref, "Mref", "pass_mref:Mref(_MvCtor)*_MvCtor"),
+        (m.pass_cptr, "Cptr", "pass_cptr:Cptr(_MvCtor)*_MvCtor"),
+        (m.pass_mptr, "Mptr", "pass_mptr:Mptr(_MvCtor)*_MvCtor"),
+        (m.pass_shmp, "Shmp", "pass_shmp:Shmp(_MvCtor)*_MvCtor"),
+        (m.pass_shcp, "Shcp", "pass_shcp:Shcp(_MvCtor)*_MvCtor"),
+        (m.pass_uqmp, "Uqmp", "pass_uqmp:Uqmp(_MvCtor)*_MvCtor"),
+        (m.pass_uqcp, "Uqcp", "pass_uqcp:Uqcp(_MvCtor)*_MvCtor"),
+    ],
+)
+def test_load_with_mtxt(pass_f, mtxt, expected):
+    assert re.match(expected, pass_f(m.atyp(mtxt)))
+
+
+@pytest.mark.parametrize(
+    ("pass_f", "rtrn_f", "expected"),
+    [
+        (m.pass_udmp, m.rtrn_udmp, "pass_udmp:rtrn_udmp"),
+        (m.pass_udcp, m.rtrn_udcp, "pass_udcp:rtrn_udcp"),
+    ],
+)
+def test_load_with_rtrn_f(pass_f, rtrn_f, expected):
+    assert pass_f(rtrn_f()) == expected
+
+
+@pytest.mark.parametrize(
+    ("pass_f", "rtrn_f", "regex_expected"),
+    [
+        (
+            m.pass_udmp_del,
+            m.rtrn_udmp_del,
+            "pass_udmp_del:rtrn_udmp_del,udmp_deleter(_MvCtorTo)*_MvCtorTo",
+        ),
+        (
+            m.pass_udcp_del,
+            m.rtrn_udcp_del,
+            "pass_udcp_del:rtrn_udcp_del,udcp_deleter(_MvCtorTo)*_MvCtorTo",
+        ),
+        (
+            m.pass_udmp_del_nd,
+            m.rtrn_udmp_del_nd,
+            "pass_udmp_del_nd:rtrn_udmp_del_nd,udmp_deleter_nd(_MvCtorTo)*_MvCtorTo",
+        ),
+        (
+            m.pass_udcp_del_nd,
+            m.rtrn_udcp_del_nd,
+            "pass_udcp_del_nd:rtrn_udcp_del_nd,udcp_deleter_nd(_MvCtorTo)*_MvCtorTo",
+        ),
+    ],
+)
+def test_deleter_roundtrip(pass_f, rtrn_f, regex_expected):
+    assert re.match(regex_expected, pass_f(rtrn_f()))
+
+
+@pytest.mark.parametrize(
+    ("pass_f", "rtrn_f", "expected"),
+    [
+        (m.pass_uqmp, m.rtrn_uqmp, "pass_uqmp:rtrn_uqmp"),
+        (m.pass_uqcp, m.rtrn_uqcp, "pass_uqcp:rtrn_uqcp"),
+        (m.pass_udmp, m.rtrn_udmp, "pass_udmp:rtrn_udmp"),
+        (m.pass_udcp, m.rtrn_udcp, "pass_udcp:rtrn_udcp"),
+    ],
+)
+def test_pass_unique_ptr_disowns(pass_f, rtrn_f, expected):
+    obj = rtrn_f()
+    assert pass_f(obj) == expected
+    with pytest.raises(ValueError) as exc_info:
+        pass_f(obj)
+    assert str(exc_info.value) == (
+        "Missing value for wrapped C++ type"
+        + " `pybind11_tests::class_sh_basic::atyp`:"
+        + " Python instance was disowned."
+    )
+
+
+@pytest.mark.parametrize(
+    ("pass_f", "rtrn_f"),
+    [
+        (m.pass_uqmp, m.rtrn_uqmp),
+        (m.pass_uqcp, m.rtrn_uqcp),
+        (m.pass_udmp, m.rtrn_udmp),
+        (m.pass_udcp, m.rtrn_udcp),
+    ],
+)
+def test_cannot_disown_use_count_ne_1(pass_f, rtrn_f):
+    obj = rtrn_f()
+    stash = m.SharedPtrStash()
+    stash.Add(obj)
+    with pytest.raises(ValueError) as exc_info:
+        pass_f(obj)
+    assert str(exc_info.value) == ("Cannot disown use_count != 1 (load_as_unique_ptr).")
+
+
+def test_unique_ptr_roundtrip(num_round_trips=1000):
+    # Multiple roundtrips to stress-test instance registration/deregistration.
+    recycled = m.atyp("passenger")
+    for _ in range(num_round_trips):
+        id_orig = id(recycled)
+        recycled = m.unique_ptr_roundtrip(recycled)
+        assert re.match("passenger(_MvCtor)*_MvCtor", m.get_mtxt(recycled))
+        id_rtrn = id(recycled)
+        # Ensure the returned object is a different Python instance.
+        assert id_rtrn != id_orig
+        id_orig = id_rtrn
+
+
+def test_pass_unique_ptr_cref():
+    obj = m.atyp("ctor_arg")
+    assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj))
+    assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.pass_unique_ptr_cref(obj))
+    assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj))
+
+
+def test_rtrn_unique_ptr_cref():
+    obj0 = m.rtrn_unique_ptr_cref("")
+    assert m.get_mtxt(obj0) == "static_ctor_arg"
+    obj1 = m.rtrn_unique_ptr_cref("passed_mtxt_1")
+    assert m.get_mtxt(obj1) == "passed_mtxt_1"
+    assert m.get_mtxt(obj0) == "passed_mtxt_1"
+    assert obj0 is obj1
+
+
+def test_unique_ptr_cref_roundtrip(num_round_trips=1000):
+    # Multiple roundtrips to stress-test implementation.
+    orig = m.atyp("passenger")
+    mtxt_orig = m.get_mtxt(orig)
+    recycled = orig
+    for _ in range(num_round_trips):
+        recycled = m.unique_ptr_cref_roundtrip(recycled)
+        assert recycled is orig
+        assert m.get_mtxt(recycled) == mtxt_orig
+
+
+@pytest.mark.parametrize(
+    ("pass_f", "rtrn_f", "moved_out", "moved_in"),
+    [
+        (m.uconsumer.pass_valu, m.uconsumer.rtrn_valu, True, True),
+        (m.uconsumer.pass_rref, m.uconsumer.rtrn_valu, True, True),
+        (m.uconsumer.pass_valu, m.uconsumer.rtrn_lref, True, False),
+        (m.uconsumer.pass_valu, m.uconsumer.rtrn_cref, True, False),
+    ],
+)
+def test_unique_ptr_consumer_roundtrip(pass_f, rtrn_f, moved_out, moved_in):
+    c = m.uconsumer()
+    assert not c.valid()
+    recycled = m.atyp("passenger")
+    mtxt_orig = m.get_mtxt(recycled)
+    assert re.match("passenger_(MvCtor){1,2}", mtxt_orig)
+
+    pass_f(c, recycled)
+    if moved_out:
+        with pytest.raises(ValueError) as excinfo:
+            m.get_mtxt(recycled)
+        assert "Python instance was disowned" in str(excinfo.value)
+
+    recycled = rtrn_f(c)
+    assert c.valid() != moved_in
+    assert m.get_mtxt(recycled) == mtxt_orig
+
+
+def test_py_type_handle_of_atyp():
+    obj = m.py_type_handle_of_atyp()
+    assert obj.__class__.__name__ == "pybind11_type"
+
+
+def test_function_signatures(doc):
+    assert (
+        doc(m.args_shared_ptr)
+        == "args_shared_ptr(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
+    )
+    assert (
+        doc(m.args_shared_ptr_const)
+        == "args_shared_ptr_const(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
+    )
+    assert (
+        doc(m.args_unique_ptr)
+        == "args_unique_ptr(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
+    )
+    assert (
+        doc(m.args_unique_ptr_const)
+        == "args_unique_ptr_const(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
+    )
+
+
+def test_unique_ptr_return_value_policy_automatic_reference():
+    assert m.get_mtxt(m.rtrn_uq_automatic_reference()) == "rtrn_uq_automatic_reference"
+
+
+def test_pass_shared_ptr_ptr():
+    obj = m.atyp()
+    with pytest.raises(RuntimeError) as excinfo:
+        m.pass_shared_ptr_ptr(obj)
+    assert str(excinfo.value) == (
+        "Passing `std::shared_ptr<T> *` from Python to C++ is not supported"
+        " (inherently unsafe)."
+    )
+
+
+def test_unusual_op_ref():
+    # Merely to test that this still exists and built successfully.
+    assert m.CallCastUnusualOpRefConstRef().__class__.__name__ == "LocalUnusualOpRef"
+    assert m.CallCastUnusualOpRefMovable().__class__.__name__ == "LocalUnusualOpRef"
diff --git a/tests/test_class_sh_disowning.cpp b/tests/test_class_sh_disowning.cpp
new file mode 100644
index 0000000..81e17ac
--- /dev/null
+++ b/tests/test_class_sh_disowning.cpp
@@ -0,0 +1,53 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+
+namespace pybind11_tests {
+namespace class_sh_disowning {
+
+template <int SerNo> // Using int as a trick to easily generate a series of types.
+struct Atype {
+    int val = 0;
+    explicit Atype(int val_) : val{val_} {}
+    int get() const { return val * 10 + SerNo; }
+};
+
+int same_twice(std::unique_ptr<Atype<1>> at1a, std::unique_ptr<Atype<1>> at1b) {
+    return at1a->get() * 100 + at1b->get() * 10;
+}
+
+int mixed(std::unique_ptr<Atype<1>> at1, std::unique_ptr<Atype<2>> at2) {
+    return at1->get() * 200 + at2->get() * 20;
+}
+
+int overloaded(std::unique_ptr<Atype<1>> at1, int i) { return at1->get() * 30 + i; }
+int overloaded(std::unique_ptr<Atype<2>> at2, int i) { return at2->get() * 40 + i; }
+
+} // namespace class_sh_disowning
+} // namespace pybind11_tests
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<1>)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<2>)
+
+TEST_SUBMODULE(class_sh_disowning, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    using namespace pybind11_tests::class_sh_disowning;
+
+    py::classh<Atype<1>>(m, "Atype1").def(py::init<int>()).def("get", &Atype<1>::get);
+    py::classh<Atype<2>>(m, "Atype2").def(py::init<int>()).def("get", &Atype<2>::get);
+
+    m.def("same_twice", same_twice);
+
+    m.def("mixed", mixed);
+
+    m.def("overloaded", (int (*)(std::unique_ptr<Atype<1>>, int)) &overloaded);
+    m.def("overloaded", (int (*)(std::unique_ptr<Atype<2>>, int)) &overloaded);
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_disowning.py b/tests/test_class_sh_disowning.py
new file mode 100644
index 0000000..95fd30c
--- /dev/null
+++ b/tests/test_class_sh_disowning.py
@@ -0,0 +1,81 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_disowning as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+def is_disowned(obj):
+    try:
+        obj.get()
+    except ValueError:
+        return True
+    return False
+
+
+def test_same_twice():
+    while True:
+        obj1a = m.Atype1(57)
+        obj1b = m.Atype1(62)
+        assert m.same_twice(obj1a, obj1b) == (57 * 10 + 1) * 100 + (62 * 10 + 1) * 10
+        assert is_disowned(obj1a)
+        assert is_disowned(obj1b)
+        obj1c = m.Atype1(0)
+        with pytest.raises(ValueError):
+            # Disowning works for one argument, but not both.
+            m.same_twice(obj1c, obj1c)
+        assert is_disowned(obj1c)
+        return  # Comment out for manual leak checking (use `top` command).
+
+
+def test_mixed():
+    first_pass = True
+    while True:
+        obj1a = m.Atype1(90)
+        obj2a = m.Atype2(25)
+        assert m.mixed(obj1a, obj2a) == (90 * 10 + 1) * 200 + (25 * 10 + 2) * 20
+        assert is_disowned(obj1a)
+        assert is_disowned(obj2a)
+
+        # The C++ order of evaluation of function arguments is (unfortunately) unspecified:
+        # https://en.cppreference.com/w/cpp/language/eval_order
+        # Read on.
+        obj1b = m.Atype1(0)
+        with pytest.raises(ValueError):
+            # If the 1st argument is evaluated first, obj1b is disowned before the conversion for
+            # the already disowned obj2a fails as expected.
+            m.mixed(obj1b, obj2a)
+        obj2b = m.Atype2(0)
+        with pytest.raises(ValueError):
+            # If the 2nd argument is evaluated first, obj2b is disowned before the conversion for
+            # the already disowned obj1a fails as expected.
+            m.mixed(obj1a, obj2b)
+
+        # Either obj1b or obj2b was disowned in the expected failed m.mixed() calls above, but not
+        # both.
+        is_disowned_results = (is_disowned(obj1b), is_disowned(obj2b))
+        assert is_disowned_results.count(True) == 1
+        if first_pass:
+            first_pass = False
+            ix = is_disowned_results.index(True) + 1
+            print(f"\nC++ function argument {ix} is evaluated first.")
+
+        return  # Comment out for manual leak checking (use `top` command).
+
+
+def test_overloaded():
+    while True:
+        obj1 = m.Atype1(81)
+        obj2 = m.Atype2(60)
+        with pytest.raises(TypeError):
+            m.overloaded(obj1, "NotInt")
+        assert obj1.get() == 81 * 10 + 1  # Not disowned.
+        assert m.overloaded(obj1, 3) == (81 * 10 + 1) * 30 + 3
+        with pytest.raises(TypeError):
+            m.overloaded(obj2, "NotInt")
+        assert obj2.get() == 60 * 10 + 2  # Not disowned.
+        assert m.overloaded(obj2, 2) == (60 * 10 + 2) * 40 + 2
+        return  # Comment out for manual leak checking (use `top` command).
diff --git a/tests/test_class_sh_disowning_mi.cpp b/tests/test_class_sh_disowning_mi.cpp
new file mode 100644
index 0000000..6622927
--- /dev/null
+++ b/tests/test_class_sh_disowning_mi.cpp
@@ -0,0 +1,102 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+
+namespace pybind11_tests {
+namespace class_sh_disowning_mi {
+
+// Diamond inheritance (copied from test_multiple_inheritance.cpp).
+struct B {
+    int val_b = 10;
+    B() = default;
+    B(const B &) = default;
+    virtual ~B() = default;
+};
+
+struct C0 : public virtual B {
+    int val_c0 = 20;
+};
+
+struct C1 : public virtual B {
+    int val_c1 = 21;
+};
+
+struct D : public C0, public C1 {
+    int val_d = 30;
+};
+
+void disown_b(std::unique_ptr<B>) {}
+
+// test_multiple_inheritance_python
+struct Base1 {
+    explicit Base1(int i) : i(i) {}
+    int foo() const { return i; }
+    int i;
+};
+
+struct Base2 {
+    explicit Base2(int j) : j(j) {}
+    int bar() const { return j; }
+    int j;
+};
+
+int disown_base1(std::unique_ptr<Base1> b1) { return b1->i * 2000 + 1; }
+int disown_base2(std::unique_ptr<Base2> b2) { return b2->j * 2000 + 2; }
+
+} // namespace class_sh_disowning_mi
+} // namespace pybind11_tests
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::B)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::C0)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::C1)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::D)
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::Base1)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::Base2)
+
+TEST_SUBMODULE(class_sh_disowning_mi, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    using namespace pybind11_tests::class_sh_disowning_mi;
+
+    py::classh<B>(m, "B")
+        .def(py::init<>())
+        .def_readonly("val_b", &D::val_b)
+        .def("b", [](B *self) { return self; })
+        .def("get", [](const B &self) { return self.val_b; });
+
+    py::classh<C0, B>(m, "C0")
+        .def(py::init<>())
+        .def_readonly("val_c0", &D::val_c0)
+        .def("c0", [](C0 *self) { return self; })
+        .def("get", [](const C0 &self) { return self.val_b * 100 + self.val_c0; });
+
+    py::classh<C1, B>(m, "C1")
+        .def(py::init<>())
+        .def_readonly("val_c1", &D::val_c1)
+        .def("c1", [](C1 *self) { return self; })
+        .def("get", [](const C1 &self) { return self.val_b * 100 + self.val_c1; });
+
+    py::classh<D, C0, C1>(m, "D")
+        .def(py::init<>())
+        .def_readonly("val_d", &D::val_d)
+        .def("d", [](D *self) { return self; })
+        .def("get", [](const D &self) {
+            return self.val_b * 1000000 + self.val_c0 * 10000 + self.val_c1 * 100 + self.val_d;
+        });
+
+    m.def("disown_b", disown_b);
+
+    // test_multiple_inheritance_python
+    py::classh<Base1>(m, "Base1").def(py::init<int>()).def("foo", &Base1::foo);
+    py::classh<Base2>(m, "Base2").def(py::init<int>()).def("bar", &Base2::bar);
+    m.def("disown_base1", disown_base1);
+    m.def("disown_base2", disown_base2);
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_disowning_mi.py b/tests/test_class_sh_disowning_mi.py
new file mode 100644
index 0000000..7b52612
--- /dev/null
+++ b/tests/test_class_sh_disowning_mi.py
@@ -0,0 +1,249 @@
+from __future__ import annotations
+
+import pytest
+
+import env  # noqa: F401
+from pybind11_tests import class_sh_disowning_mi as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+def test_diamond_inheritance():
+    # Very similar to test_multiple_inheritance.py:test_diamond_inheritance.
+    d = m.D()
+    assert d is d.d()
+    assert d is d.c0()
+    assert d is d.c1()
+    assert d is d.b()
+    assert d is d.c0().b()
+    assert d is d.c1().b()
+    assert d is d.c0().c1().b().c0().b()
+
+
+def is_disowned(callable_method):
+    try:
+        callable_method()
+    except ValueError as e:
+        assert "Python instance was disowned" in str(e)  # noqa: PT017
+        return True
+    return False
+
+
+def test_disown_b():
+    b = m.B()
+    assert b.get() == 10
+    m.disown_b(b)
+    assert is_disowned(b.get)
+
+
+@pytest.mark.parametrize("var_to_disown", ["c0", "b"])
+def test_disown_c0(var_to_disown):
+    c0 = m.C0()
+    assert c0.get() == 1020
+    b = c0.b()
+    m.disown_b(locals()[var_to_disown])
+    assert is_disowned(c0.get)
+    assert is_disowned(b.get)
+
+
+@pytest.mark.parametrize("var_to_disown", ["c1", "b"])
+def test_disown_c1(var_to_disown):
+    c1 = m.C1()
+    assert c1.get() == 1021
+    b = c1.b()
+    m.disown_b(locals()[var_to_disown])
+    assert is_disowned(c1.get)
+    assert is_disowned(b.get)
+
+
+@pytest.mark.parametrize("var_to_disown", ["d", "c1", "c0", "b"])
+def test_disown_d(var_to_disown):
+    d = m.D()
+    assert d.get() == 10202130
+    b = d.b()
+    c0 = d.c0()
+    c1 = d.c1()
+    m.disown_b(locals()[var_to_disown])
+    assert is_disowned(d.get)
+    assert is_disowned(c1.get)
+    assert is_disowned(c0.get)
+    assert is_disowned(b.get)
+
+
+# Based on test_multiple_inheritance.py:test_multiple_inheritance_python.
+class MI1(m.Base1, m.Base2):
+    def __init__(self, i, j):
+        m.Base1.__init__(self, i)
+        m.Base2.__init__(self, j)
+
+
+class B1:
+    def v(self):
+        return 1
+
+
+class MI2(B1, m.Base1, m.Base2):
+    def __init__(self, i, j):
+        B1.__init__(self)
+        m.Base1.__init__(self, i)
+        m.Base2.__init__(self, j)
+
+
+class MI3(MI2):
+    def __init__(self, i, j):
+        MI2.__init__(self, i, j)
+
+
+class MI4(MI3, m.Base2):
+    def __init__(self, i, j):
+        MI3.__init__(self, i, j)
+        # This should be ignored (Base2 is already initialized via MI2):
+        m.Base2.__init__(self, i + 100)
+
+
+class MI5(m.Base2, B1, m.Base1):
+    def __init__(self, i, j):
+        B1.__init__(self)
+        m.Base1.__init__(self, i)
+        m.Base2.__init__(self, j)
+
+
+class MI6(m.Base2, B1):
+    def __init__(self, i):
+        m.Base2.__init__(self, i)
+        B1.__init__(self)
+
+
+class B2(B1):
+    def v(self):
+        return 2
+
+
+class B3:
+    def v(self):
+        return 3
+
+
+class B4(B3, B2):
+    def v(self):
+        return 4
+
+
+class MI7(B4, MI6):
+    def __init__(self, i):
+        B4.__init__(self)
+        MI6.__init__(self, i)
+
+
+class MI8(MI6, B3):
+    def __init__(self, i):
+        MI6.__init__(self, i)
+        B3.__init__(self)
+
+
+class MI8b(B3, MI6):
+    def __init__(self, i):
+        B3.__init__(self)
+        MI6.__init__(self, i)
+
+
+@pytest.mark.xfail("env.PYPY")
+def test_multiple_inheritance_python():
+    # Based on test_multiple_inheritance.py:test_multiple_inheritance_python.
+    # Exercises values_and_holders with 2 value_and_holder instances.
+
+    mi1 = MI1(1, 2)
+    assert mi1.foo() == 1
+    assert mi1.bar() == 2
+
+    mi2 = MI2(3, 4)
+    assert mi2.v() == 1
+    assert mi2.foo() == 3
+    assert mi2.bar() == 4
+
+    mi3 = MI3(5, 6)
+    assert mi3.v() == 1
+    assert mi3.foo() == 5
+    assert mi3.bar() == 6
+
+    mi4 = MI4(7, 8)
+    assert mi4.v() == 1
+    assert mi4.foo() == 7
+    assert mi4.bar() == 8
+
+    mi5 = MI5(10, 11)
+    assert mi5.v() == 1
+    assert mi5.foo() == 10
+    assert mi5.bar() == 11
+
+    mi6 = MI6(12)
+    assert mi6.v() == 1
+    assert mi6.bar() == 12
+
+    mi7 = MI7(13)
+    assert mi7.v() == 4
+    assert mi7.bar() == 13
+
+    mi8 = MI8(14)
+    assert mi8.v() == 1
+    assert mi8.bar() == 14
+
+    mi8b = MI8b(15)
+    assert mi8b.v() == 3
+    assert mi8b.bar() == 15
+
+
+DISOWN_CLS_I_J_V_LIST = [
+    (MI1, 1, 2, None),
+    (MI2, 3, 4, 1),
+    (MI3, 5, 6, 1),
+    (MI4, 7, 8, 1),
+    (MI5, 10, 11, 1),
+]
+
+
+@pytest.mark.xfail("env.PYPY", strict=False)
+@pytest.mark.parametrize(("cls", "i", "j", "v"), DISOWN_CLS_I_J_V_LIST)
+def test_disown_base1_first(cls, i, j, v):
+    obj = cls(i, j)
+    assert obj.foo() == i
+    assert m.disown_base1(obj) == 2000 * i + 1
+    assert is_disowned(obj.foo)
+    assert obj.bar() == j
+    assert m.disown_base2(obj) == 2000 * j + 2
+    assert is_disowned(obj.bar)
+    if v is not None:
+        assert obj.v() == v
+
+
+@pytest.mark.xfail("env.PYPY", strict=False)
+@pytest.mark.parametrize(("cls", "i", "j", "v"), DISOWN_CLS_I_J_V_LIST)
+def test_disown_base2_first(cls, i, j, v):
+    obj = cls(i, j)
+    assert obj.bar() == j
+    assert m.disown_base2(obj) == 2000 * j + 2
+    assert is_disowned(obj.bar)
+    assert obj.foo() == i
+    assert m.disown_base1(obj) == 2000 * i + 1
+    assert is_disowned(obj.foo)
+    if v is not None:
+        assert obj.v() == v
+
+
+@pytest.mark.xfail("env.PYPY", strict=False)
+@pytest.mark.parametrize(
+    ("cls", "j", "v"),
+    [
+        (MI6, 12, 1),
+        (MI7, 13, 4),
+        (MI8, 14, 1),
+        (MI8b, 15, 3),
+    ],
+)
+def test_disown_base2(cls, j, v):
+    obj = cls(j)
+    assert obj.bar() == j
+    assert m.disown_base2(obj) == 2000 * j + 2
+    assert is_disowned(obj.bar)
+    assert obj.v() == v
diff --git a/tests/test_class_sh_factory_constructors.cpp b/tests/test_class_sh_factory_constructors.cpp
new file mode 100644
index 0000000..f4c0c1d
--- /dev/null
+++ b/tests/test_class_sh_factory_constructors.cpp
@@ -0,0 +1,187 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+#include <string>
+
+namespace pybind11_tests {
+namespace class_sh_factory_constructors {
+
+template <int> // Using int as a trick to easily generate a series of types.
+struct atyp {  // Short for "any type".
+    std::string mtxt;
+};
+
+template <typename T>
+std::string get_mtxt(const T &obj) {
+    return obj.mtxt;
+}
+
+using atyp_valu = atyp<0x0>;
+using atyp_rref = atyp<0x1>;
+using atyp_cref = atyp<0x2>;
+using atyp_mref = atyp<0x3>;
+using atyp_cptr = atyp<0x4>;
+using atyp_mptr = atyp<0x5>;
+using atyp_shmp = atyp<0x6>;
+using atyp_shcp = atyp<0x7>;
+using atyp_uqmp = atyp<0x8>;
+using atyp_uqcp = atyp<0x9>;
+using atyp_udmp = atyp<0xA>;
+using atyp_udcp = atyp<0xB>;
+
+// clang-format off
+
+atyp_valu        rtrn_valu() { atyp_valu obj{"Valu"}; return obj; }
+atyp_rref&&      rtrn_rref() { static atyp_rref obj; obj.mtxt = "Rref"; return std::move(obj); }
+atyp_cref const& rtrn_cref() { static atyp_cref obj; obj.mtxt = "Cref"; return obj; }
+atyp_mref&       rtrn_mref() { static atyp_mref obj; obj.mtxt = "Mref"; return obj; }
+atyp_cptr const* rtrn_cptr() { return new atyp_cptr{"Cptr"}; }
+atyp_mptr*       rtrn_mptr() { return new atyp_mptr{"Mptr"}; }
+
+std::shared_ptr<atyp_shmp>       rtrn_shmp() { return std::make_shared<atyp_shmp>(atyp_shmp{"Shmp"}); }
+std::shared_ptr<atyp_shcp const> rtrn_shcp() { return std::shared_ptr<atyp_shcp const>(new atyp_shcp{"Shcp"}); }
+
+std::unique_ptr<atyp_uqmp>       rtrn_uqmp() { return std::unique_ptr<atyp_uqmp      >(new atyp_uqmp{"Uqmp"}); }
+std::unique_ptr<atyp_uqcp const> rtrn_uqcp() { return std::unique_ptr<atyp_uqcp const>(new atyp_uqcp{"Uqcp"}); }
+
+struct sddm : std::default_delete<atyp_udmp      > {};
+struct sddc : std::default_delete<atyp_udcp const> {};
+
+std::unique_ptr<atyp_udmp,       sddm> rtrn_udmp() { return std::unique_ptr<atyp_udmp,       sddm>(new atyp_udmp{"Udmp"}); }
+std::unique_ptr<atyp_udcp const, sddc> rtrn_udcp() { return std::unique_ptr<atyp_udcp const, sddc>(new atyp_udcp{"Udcp"}); }
+
+// clang-format on
+
+// Minimalistic approach to achieve full coverage of construct() overloads for constructing
+// smart_holder from unique_ptr and shared_ptr returns.
+struct with_alias {
+    int val = 0;
+    virtual ~with_alias() = default;
+    // Some compilers complain about implicitly defined versions of some of the following:
+    with_alias() = default;
+    with_alias(const with_alias &) = default;
+    with_alias(with_alias &&) = default;
+    with_alias &operator=(const with_alias &) = default;
+    with_alias &operator=(with_alias &&) = default;
+};
+struct with_alias_alias : with_alias {};
+struct sddwaa : std::default_delete<with_alias_alias> {};
+
+} // namespace class_sh_factory_constructors
+} // namespace pybind11_tests
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_valu)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_rref)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_cref)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_mref)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_cptr)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_mptr)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_shmp)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_shcp)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_uqmp)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_uqcp)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_udmp)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_udcp)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::with_alias)
+
+TEST_SUBMODULE(class_sh_factory_constructors, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    using namespace pybind11_tests::class_sh_factory_constructors;
+
+    py::classh<atyp_valu>(m, "atyp_valu")
+        .def(py::init(&rtrn_valu))
+        .def("get_mtxt", get_mtxt<atyp_valu>);
+
+    py::classh<atyp_rref>(m, "atyp_rref")
+        .def(py::init(&rtrn_rref))
+        .def("get_mtxt", get_mtxt<atyp_rref>);
+
+    py::classh<atyp_cref>(m, "atyp_cref")
+        // class_: ... must return a compatible ...
+        // classh: ... cannot pass object of non-trivial type ...
+        // .def(py::init(&rtrn_cref))
+        .def("get_mtxt", get_mtxt<atyp_cref>);
+
+    py::classh<atyp_mref>(m, "atyp_mref")
+        // class_: ... must return a compatible ...
+        // classh: ... cannot pass object of non-trivial type ...
+        // .def(py::init(&rtrn_mref))
+        .def("get_mtxt", get_mtxt<atyp_mref>);
+
+    py::classh<atyp_cptr>(m, "atyp_cptr")
+        // class_: ... must return a compatible ...
+        // classh: ... must return a compatible ...
+        // .def(py::init(&rtrn_cptr))
+        .def("get_mtxt", get_mtxt<atyp_cptr>);
+
+    py::classh<atyp_mptr>(m, "atyp_mptr")
+        .def(py::init(&rtrn_mptr))
+        .def("get_mtxt", get_mtxt<atyp_mptr>);
+
+    py::classh<atyp_shmp>(m, "atyp_shmp")
+        .def(py::init(&rtrn_shmp))
+        .def("get_mtxt", get_mtxt<atyp_shmp>);
+
+    py::classh<atyp_shcp>(m, "atyp_shcp")
+        // py::class_<atyp_shcp, std::shared_ptr<atyp_shcp>>(m, "atyp_shcp")
+        // class_: ... must return a compatible ...
+        // classh: ... cannot pass object of non-trivial type ...
+        // .def(py::init(&rtrn_shcp))
+        .def("get_mtxt", get_mtxt<atyp_shcp>);
+
+    py::classh<atyp_uqmp>(m, "atyp_uqmp")
+        .def(py::init(&rtrn_uqmp))
+        .def("get_mtxt", get_mtxt<atyp_uqmp>);
+
+    py::classh<atyp_uqcp>(m, "atyp_uqcp")
+        // class_: ... cannot pass object of non-trivial type ...
+        // classh: ... cannot pass object of non-trivial type ...
+        // .def(py::init(&rtrn_uqcp))
+        .def("get_mtxt", get_mtxt<atyp_uqcp>);
+
+    py::classh<atyp_udmp>(m, "atyp_udmp")
+        .def(py::init(&rtrn_udmp))
+        .def("get_mtxt", get_mtxt<atyp_udmp>);
+
+    py::classh<atyp_udcp>(m, "atyp_udcp")
+        // py::class_<atyp_udcp, std::unique_ptr<atyp_udcp, sddc>>(m, "atyp_udcp")
+        // class_: ... must return a compatible ...
+        // classh: ... cannot pass object of non-trivial type ...
+        // .def(py::init(&rtrn_udcp))
+        .def("get_mtxt", get_mtxt<atyp_udcp>);
+
+    py::classh<with_alias, with_alias_alias>(m, "with_alias")
+        .def_readonly("val", &with_alias::val)
+        .def(py::init([](int i) {
+            auto p = std::unique_ptr<with_alias_alias, sddwaa>(new with_alias_alias);
+            p->val = i * 100;
+            return p;
+        }))
+        .def(py::init([](int i, int j) {
+            auto p = std::unique_ptr<with_alias_alias>(new with_alias_alias);
+            p->val = i * 100 + j * 10;
+            return p;
+        }))
+        .def(py::init([](int i, int j, int k) {
+            auto p = std::make_shared<with_alias_alias>();
+            p->val = i * 100 + j * 10 + k;
+            return p;
+        }))
+        .def(py::init(
+            [](int, int, int, int) { return std::unique_ptr<with_alias>(new with_alias); },
+            [](int, int, int, int) {
+                return std::unique_ptr<with_alias>(new with_alias); // Invalid alias factory.
+            }))
+        .def(py::init([](int, int, int, int, int) { return std::make_shared<with_alias>(); },
+                      [](int, int, int, int, int) {
+                          return std::make_shared<with_alias>(); // Invalid alias factory.
+                      }));
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_factory_constructors.py b/tests/test_class_sh_factory_constructors.py
new file mode 100644
index 0000000..149a334
--- /dev/null
+++ b/tests/test_class_sh_factory_constructors.py
@@ -0,0 +1,56 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_factory_constructors as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+def test_atyp_factories():
+    assert m.atyp_valu().get_mtxt() == "Valu"
+    assert m.atyp_rref().get_mtxt() == "Rref"
+    # sert m.atyp_cref().get_mtxt() == "Cref"
+    # sert m.atyp_mref().get_mtxt() == "Mref"
+    # sert m.atyp_cptr().get_mtxt() == "Cptr"
+    assert m.atyp_mptr().get_mtxt() == "Mptr"
+    assert m.atyp_shmp().get_mtxt() == "Shmp"
+    # sert m.atyp_shcp().get_mtxt() == "Shcp"
+    assert m.atyp_uqmp().get_mtxt() == "Uqmp"
+    # sert m.atyp_uqcp().get_mtxt() == "Uqcp"
+    assert m.atyp_udmp().get_mtxt() == "Udmp"
+    # sert m.atyp_udcp().get_mtxt() == "Udcp"
+
+
+@pytest.mark.parametrize(
+    ("init_args", "expected"),
+    [
+        ((3,), 300),
+        ((5, 7), 570),
+        ((9, 11, 13), 1023),
+    ],
+)
+def test_with_alias_success(init_args, expected):
+    assert m.with_alias(*init_args).val == expected
+
+
+@pytest.mark.parametrize(
+    ("num_init_args", "smart_ptr"),
+    [
+        (4, "std::unique_ptr"),
+        (5, "std::shared_ptr"),
+    ],
+)
+def test_with_alias_invalid(num_init_args, smart_ptr):
+    class PyDrvdWithAlias(m.with_alias):
+        pass
+
+    with pytest.raises(TypeError) as excinfo:
+        PyDrvdWithAlias(*((0,) * num_init_args))
+    assert (
+        str(excinfo.value)
+        == "pybind11::init(): construction failed: returned "
+        + smart_ptr
+        + " pointee is not an alias instance"
+    )
diff --git a/tests/test_class_sh_inheritance.cpp b/tests/test_class_sh_inheritance.cpp
new file mode 100644
index 0000000..86ecea9
--- /dev/null
+++ b/tests/test_class_sh_inheritance.cpp
@@ -0,0 +1,112 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+
+namespace pybind11_tests {
+namespace class_sh_inheritance {
+
+template <int Id>
+struct base_template {
+    base_template() : base_id(Id) {}
+    virtual ~base_template() = default;
+    virtual int id() const { return base_id; }
+    int base_id;
+
+    // Some compilers complain about implicitly defined versions of some of the following:
+    base_template(const base_template &) = default;
+    base_template(base_template &&) noexcept = default;
+    base_template &operator=(const base_template &) = default;
+    base_template &operator=(base_template &&) noexcept = default;
+};
+
+using base = base_template<100>;
+
+struct drvd : base {
+    int id() const override { return 2 * base_id; }
+};
+
+// clang-format off
+inline drvd *rtrn_mptr_drvd()         { return new drvd; }
+inline base *rtrn_mptr_drvd_up_cast() { return new drvd; }
+
+inline int pass_cptr_base(base const *b) { return b->id() + 11; }
+inline int pass_cptr_drvd(drvd const *d) { return d->id() + 12; }
+
+inline std::shared_ptr<drvd> rtrn_shmp_drvd()         { return std::make_shared<drvd>(); }
+inline std::shared_ptr<base> rtrn_shmp_drvd_up_cast() { return std::make_shared<drvd>(); }
+
+inline int pass_shcp_base(const std::shared_ptr<base const>& b) { return b->id() + 21; }
+inline int pass_shcp_drvd(const std::shared_ptr<drvd const>& d) { return d->id() + 22; }
+// clang-format on
+
+using base1 = base_template<110>;
+using base2 = base_template<120>;
+
+// Not reusing base here because it would interfere with the single-inheritance test.
+struct drvd2 : base1, base2 {
+    int id() const override { return 3 * base1::base_id + 4 * base2::base_id; }
+};
+
+// clang-format off
+inline drvd2 *rtrn_mptr_drvd2()          { return new drvd2; }
+inline base1 *rtrn_mptr_drvd2_up_cast1() { return new drvd2; }
+inline base2 *rtrn_mptr_drvd2_up_cast2() { return new drvd2; }
+
+inline int pass_cptr_base1(base1 const *b) { return b->id() + 21; }
+inline int pass_cptr_base2(base2 const *b) { return b->id() + 22; }
+inline int pass_cptr_drvd2(drvd2 const *d) { return d->id() + 23; }
+// clang-format on
+
+} // namespace class_sh_inheritance
+} // namespace pybind11_tests
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::base)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::drvd)
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::base1)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::base2)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::drvd2)
+
+namespace pybind11_tests {
+namespace class_sh_inheritance {
+
+TEST_SUBMODULE(class_sh_inheritance, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    py::classh<base>(m, "base");
+    py::classh<drvd, base>(m, "drvd");
+
+    auto rvto = py::return_value_policy::take_ownership;
+
+    m.def("rtrn_mptr_drvd", rtrn_mptr_drvd, rvto);
+    m.def("rtrn_mptr_drvd_up_cast", rtrn_mptr_drvd_up_cast, rvto);
+    m.def("pass_cptr_base", pass_cptr_base);
+    m.def("pass_cptr_drvd", pass_cptr_drvd);
+
+    m.def("rtrn_shmp_drvd", rtrn_shmp_drvd);
+    m.def("rtrn_shmp_drvd_up_cast", rtrn_shmp_drvd_up_cast);
+    m.def("pass_shcp_base", pass_shcp_base);
+    m.def("pass_shcp_drvd", pass_shcp_drvd);
+
+    // __init__ needed for Python inheritance.
+    py::classh<base1>(m, "base1").def(py::init<>());
+    py::classh<base2>(m, "base2").def(py::init<>());
+    py::classh<drvd2, base1, base2>(m, "drvd2");
+
+    m.def("rtrn_mptr_drvd2", rtrn_mptr_drvd2, rvto);
+    m.def("rtrn_mptr_drvd2_up_cast1", rtrn_mptr_drvd2_up_cast1, rvto);
+    m.def("rtrn_mptr_drvd2_up_cast2", rtrn_mptr_drvd2_up_cast2, rvto);
+    m.def("pass_cptr_base1", pass_cptr_base1);
+    m.def("pass_cptr_base2", pass_cptr_base2);
+    m.def("pass_cptr_drvd2", pass_cptr_drvd2);
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
+
+} // namespace class_sh_inheritance
+} // namespace pybind11_tests
diff --git a/tests/test_class_sh_inheritance.py b/tests/test_class_sh_inheritance.py
new file mode 100644
index 0000000..4f8c0e6
--- /dev/null
+++ b/tests/test_class_sh_inheritance.py
@@ -0,0 +1,68 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_inheritance as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+def test_rtrn_mptr_drvd_pass_cptr_base():
+    d = m.rtrn_mptr_drvd()
+    i = m.pass_cptr_base(d)  # load_impl Case 2a
+    assert i == 2 * 100 + 11
+
+
+def test_rtrn_shmp_drvd_pass_shcp_base():
+    d = m.rtrn_shmp_drvd()
+    i = m.pass_shcp_base(d)  # load_impl Case 2a
+    assert i == 2 * 100 + 21
+
+
+def test_rtrn_mptr_drvd_up_cast_pass_cptr_drvd():
+    b = m.rtrn_mptr_drvd_up_cast()
+    # the base return is down-cast immediately.
+    assert b.__class__.__name__ == "drvd"
+    i = m.pass_cptr_drvd(b)
+    assert i == 2 * 100 + 12
+
+
+def test_rtrn_shmp_drvd_up_cast_pass_shcp_drvd():
+    b = m.rtrn_shmp_drvd_up_cast()
+    # the base return is down-cast immediately.
+    assert b.__class__.__name__ == "drvd"
+    i = m.pass_shcp_drvd(b)
+    assert i == 2 * 100 + 22
+
+
+def test_rtrn_mptr_drvd2_pass_cptr_bases():
+    d = m.rtrn_mptr_drvd2()
+    i1 = m.pass_cptr_base1(d)  # load_impl Case 2c
+    assert i1 == 3 * 110 + 4 * 120 + 21
+    i2 = m.pass_cptr_base2(d)
+    assert i2 == 3 * 110 + 4 * 120 + 22
+
+
+def test_rtrn_mptr_drvd2_up_casts_pass_cptr_drvd2():
+    b1 = m.rtrn_mptr_drvd2_up_cast1()
+    assert b1.__class__.__name__ == "drvd2"
+    i1 = m.pass_cptr_drvd2(b1)
+    assert i1 == 3 * 110 + 4 * 120 + 23
+    b2 = m.rtrn_mptr_drvd2_up_cast2()
+    assert b2.__class__.__name__ == "drvd2"
+    i2 = m.pass_cptr_drvd2(b2)
+    assert i2 == 3 * 110 + 4 * 120 + 23
+
+
+def test_python_drvd2():
+    class Drvd2(m.base1, m.base2):
+        def __init__(self):
+            m.base1.__init__(self)
+            m.base2.__init__(self)
+
+    d = Drvd2()
+    i1 = m.pass_cptr_base1(d)  # load_impl Case 2b
+    assert i1 == 110 + 21
+    i2 = m.pass_cptr_base2(d)
+    assert i2 == 120 + 22
diff --git a/tests/test_class_sh_mi_thunks.cpp b/tests/test_class_sh_mi_thunks.cpp
new file mode 100644
index 0000000..2630ab0
--- /dev/null
+++ b/tests/test_class_sh_mi_thunks.cpp
@@ -0,0 +1,107 @@
+#include <pybind11/pybind11.h>
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+namespace test_class_sh_mi_thunks {
+
+// For general background: https://shaharmike.com/cpp/vtable-part2/
+// C++ vtables - Part 2 - Multiple Inheritance
+// ... the compiler creates a 'thunk' method that corrects `this` ...
+
+struct Base0 {
+    virtual ~Base0() = default;
+    Base0() = default;
+    Base0(const Base0 &) = delete;
+};
+
+struct Base1 {
+    virtual ~Base1() = default;
+    // Using `vector` here because it is known to make this test very sensitive to bugs.
+    std::vector<int> vec = {1, 2, 3, 4, 5};
+    Base1() = default;
+    Base1(const Base1 &) = delete;
+};
+
+struct Derived : Base1, Base0 {
+    ~Derived() override = default;
+    Derived() = default;
+    Derived(const Derived &) = delete;
+};
+
+} // namespace test_class_sh_mi_thunks
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_mi_thunks::Base0)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_mi_thunks::Base1)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_mi_thunks::Derived)
+
+TEST_SUBMODULE(class_sh_mi_thunks, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    using namespace test_class_sh_mi_thunks;
+
+    m.def("ptrdiff_drvd_base0", []() {
+        auto drvd = std::unique_ptr<Derived>(new Derived);
+        auto *base0 = dynamic_cast<Base0 *>(drvd.get());
+        return std::ptrdiff_t(reinterpret_cast<char *>(drvd.get())
+                              - reinterpret_cast<char *>(base0));
+    });
+
+    py::classh<Base0>(m, "Base0");
+    py::classh<Base1>(m, "Base1");
+    py::classh<Derived, Base1, Base0>(m, "Derived");
+
+    m.def(
+        "get_drvd_as_base0_raw_ptr",
+        []() {
+            auto *drvd = new Derived;
+            auto *base0 = dynamic_cast<Base0 *>(drvd);
+            return base0;
+        },
+        py::return_value_policy::take_ownership);
+
+    m.def("get_drvd_as_base0_shared_ptr", []() {
+        auto drvd = std::make_shared<Derived>();
+        auto base0 = std::dynamic_pointer_cast<Base0>(drvd);
+        return base0;
+    });
+
+    m.def("get_drvd_as_base0_unique_ptr", []() {
+        auto drvd = std::unique_ptr<Derived>(new Derived);
+        auto base0 = std::unique_ptr<Base0>(std::move(drvd));
+        return base0;
+    });
+
+    m.def("vec_size_base0_raw_ptr", [](const Base0 *obj) {
+        const auto *obj_der = dynamic_cast<const Derived *>(obj);
+        if (obj_der == nullptr) {
+            return std::size_t(0);
+        }
+        return obj_der->vec.size();
+    });
+
+    m.def("vec_size_base0_shared_ptr", [](const std::shared_ptr<Base0> &obj) -> std::size_t {
+        const auto obj_der = std::dynamic_pointer_cast<Derived>(obj);
+        if (!obj_der) {
+            return std::size_t(0);
+        }
+        return obj_der->vec.size();
+    });
+
+    m.def("vec_size_base0_unique_ptr", [](std::unique_ptr<Base0> obj) -> std::size_t {
+        const auto *obj_der = dynamic_cast<const Derived *>(obj.get());
+        if (obj_der == nullptr) {
+            return std::size_t(0);
+        }
+        return obj_der->vec.size();
+    });
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_mi_thunks.py b/tests/test_class_sh_mi_thunks.py
new file mode 100644
index 0000000..af3971b
--- /dev/null
+++ b/tests/test_class_sh_mi_thunks.py
@@ -0,0 +1,56 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_mi_thunks as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+def test_ptrdiff_drvd_base0():
+    ptrdiff = m.ptrdiff_drvd_base0()
+    # A failure here does not (necessarily) mean that there is a bug, but that
+    # test_class_sh_mi_thunks is not exercising what it is supposed to.
+    # If this ever fails on some platforms: use pytest.skip()
+    # If this ever fails on all platforms: don't know, seems extremely unlikely.
+    assert ptrdiff != 0
+
+
+@pytest.mark.parametrize(
+    "vec_size_fn",
+    [
+        m.vec_size_base0_raw_ptr,
+        m.vec_size_base0_shared_ptr,
+    ],
+)
+@pytest.mark.parametrize(
+    "get_fn",
+    [
+        m.get_drvd_as_base0_raw_ptr,
+        m.get_drvd_as_base0_shared_ptr,
+        m.get_drvd_as_base0_unique_ptr,
+    ],
+)
+def test_get_vec_size_raw_shared(get_fn, vec_size_fn):
+    obj = get_fn()
+    assert vec_size_fn(obj) == 5
+
+
+@pytest.mark.parametrize(
+    "get_fn", [m.get_drvd_as_base0_raw_ptr, m.get_drvd_as_base0_unique_ptr]
+)
+def test_get_vec_size_unique(get_fn):
+    obj = get_fn()
+    assert m.vec_size_base0_unique_ptr(obj) == 5
+    with pytest.raises(ValueError, match="Python instance was disowned"):
+        m.vec_size_base0_unique_ptr(obj)
+
+
+def test_get_shared_vec_size_unique():
+    obj = m.get_drvd_as_base0_shared_ptr()
+    with pytest.raises(ValueError) as exc_info:
+        m.vec_size_base0_unique_ptr(obj)
+    assert (
+        str(exc_info.value) == "Cannot disown external shared_ptr (load_as_unique_ptr)."
+    )
diff --git a/tests/test_class_sh_property.cpp b/tests/test_class_sh_property.cpp
new file mode 100644
index 0000000..22a1f69
--- /dev/null
+++ b/tests/test_class_sh_property.cpp
@@ -0,0 +1,113 @@
+// The compact 4-character naming matches that in test_class_sh_basic.cpp
+// Variable names are intentionally terse, to not distract from the more important C++ type names:
+// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const,
+// sh = shared_ptr, uq = unique_ptr.
+
+#include "pybind11/smart_holder.h"
+#include "pybind11_tests.h"
+
+#include <memory>
+
+namespace test_class_sh_property {
+
+struct ClassicField {
+    int num = -88;
+};
+
+struct ClassicOuter {
+    ClassicField *m_mptr = nullptr;
+    const ClassicField *m_cptr = nullptr;
+};
+
+struct Field {
+    int num = -99;
+};
+
+struct Outer {
+    Field m_valu;
+    Field *m_mptr = nullptr;
+    const Field *m_cptr = nullptr;
+    std::unique_ptr<Field> m_uqmp;
+    std::unique_ptr<const Field> m_uqcp;
+    std::shared_ptr<Field> m_shmp;
+    std::shared_ptr<const Field> m_shcp;
+};
+
+inline void DisownOuter(std::unique_ptr<Outer>) {}
+
+struct WithCharArrayMember {
+    WithCharArrayMember() { std::memcpy(char6_member, "Char6", 6); }
+    char char6_member[6];
+};
+
+struct WithConstCharPtrMember {
+    const char *const_char_ptr_member = "ConstChar*";
+};
+
+} // namespace test_class_sh_property
+
+PYBIND11_TYPE_CASTER_BASE_HOLDER(test_class_sh_property::ClassicField,
+                                 std::unique_ptr<test_class_sh_property::ClassicField>)
+PYBIND11_TYPE_CASTER_BASE_HOLDER(test_class_sh_property::ClassicOuter,
+                                 std::unique_ptr<test_class_sh_property::ClassicOuter>)
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::Field)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::Outer)
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::WithCharArrayMember)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::WithConstCharPtrMember)
+
+TEST_SUBMODULE(class_sh_property, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    using namespace test_class_sh_property;
+
+    py::class_<ClassicField, std::unique_ptr<ClassicField>>(m, "ClassicField")
+        .def(py::init<>())
+        .def_readwrite("num", &ClassicField::num);
+
+    py::class_<ClassicOuter, std::unique_ptr<ClassicOuter>>(m, "ClassicOuter")
+        .def(py::init<>())
+        .def_readonly("m_mptr_readonly", &ClassicOuter::m_mptr)
+        .def_readwrite("m_mptr_readwrite", &ClassicOuter::m_mptr)
+        .def_readwrite("m_cptr_readonly", &ClassicOuter::m_cptr)
+        .def_readwrite("m_cptr_readwrite", &ClassicOuter::m_cptr);
+
+    py::classh<Field>(m, "Field").def(py::init<>()).def_readwrite("num", &Field::num);
+
+    py::classh<Outer>(m, "Outer")
+        .def(py::init<>())
+
+        .def_readonly("m_valu_readonly", &Outer::m_valu)
+        .def_readwrite("m_valu_readwrite", &Outer::m_valu)
+
+        .def_readonly("m_mptr_readonly", &Outer::m_mptr)
+        .def_readwrite("m_mptr_readwrite", &Outer::m_mptr)
+        .def_readonly("m_cptr_readonly", &Outer::m_cptr)
+        .def_readwrite("m_cptr_readwrite", &Outer::m_cptr)
+
+        // .def_readonly("m_uqmp_readonly", &Outer::m_uqmp) // Custom compilation Error.
+        .def_readwrite("m_uqmp_readwrite", &Outer::m_uqmp)
+        // .def_readonly("m_uqcp_readonly", &Outer::m_uqcp) // Custom compilation Error.
+        .def_readwrite("m_uqcp_readwrite", &Outer::m_uqcp)
+
+        .def_readwrite("m_shmp_readonly", &Outer::m_shmp)
+        .def_readwrite("m_shmp_readwrite", &Outer::m_shmp)
+        .def_readwrite("m_shcp_readonly", &Outer::m_shcp)
+        .def_readwrite("m_shcp_readwrite", &Outer::m_shcp);
+
+    m.def("DisownOuter", DisownOuter);
+
+    py::classh<WithCharArrayMember>(m, "WithCharArrayMember")
+        .def(py::init<>())
+        .def_readonly("char6_member", &WithCharArrayMember::char6_member);
+
+    py::classh<WithConstCharPtrMember>(m, "WithConstCharPtrMember")
+        .def(py::init<>())
+        .def_readonly("const_char_ptr_member", &WithConstCharPtrMember::const_char_ptr_member);
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_property.py b/tests/test_class_sh_property.py
new file mode 100644
index 0000000..9bd2fbf
--- /dev/null
+++ b/tests/test_class_sh_property.py
@@ -0,0 +1,169 @@
+# The compact 4-character naming scheme (e.g. mptr, cptr, shcp) is explained at the top of
+# test_class_sh_property.cpp.
+from __future__ import annotations
+
+import pytest
+
+import env  # noqa: F401
+from pybind11_tests import class_sh_property as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+@pytest.mark.skipif(
+    "env.PYPY or env.GRAALPY", reason="gc after `del field` is apparently deferred"
+)
+@pytest.mark.parametrize("m_attr", ["m_valu_readonly", "m_valu_readwrite"])
+def test_valu_getter(m_attr):
+    # Reduced from PyCLIF test:
+    # https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/testing/python/nested_fields_test.py#L56
+    outer = m.Outer()
+    field = getattr(outer, m_attr)
+    assert field.num == -99
+    with pytest.raises(ValueError) as excinfo:
+        m.DisownOuter(outer)
+    assert str(excinfo.value) == "Cannot disown use_count != 1 (load_as_unique_ptr)."
+    del field
+    m.DisownOuter(outer)
+    with pytest.raises(ValueError, match="Python instance was disowned") as excinfo:
+        getattr(outer, m_attr)
+
+
+def test_valu_setter():
+    outer = m.Outer()
+    assert outer.m_valu_readonly.num == -99
+    assert outer.m_valu_readwrite.num == -99
+    field = m.Field()
+    field.num = 35
+    outer.m_valu_readwrite = field
+    assert outer.m_valu_readonly.num == 35
+    assert outer.m_valu_readwrite.num == 35
+
+
+@pytest.mark.parametrize("m_attr", ["m_shmp", "m_shcp"])
+def test_shp(m_attr):
+    m_attr_readonly = m_attr + "_readonly"
+    m_attr_readwrite = m_attr + "_readwrite"
+    outer = m.Outer()
+    assert getattr(outer, m_attr_readonly) is None
+    assert getattr(outer, m_attr_readwrite) is None
+    field = m.Field()
+    field.num = 43
+    setattr(outer, m_attr_readwrite, field)
+    assert getattr(outer, m_attr_readonly).num == 43
+    assert getattr(outer, m_attr_readwrite).num == 43
+    getattr(outer, m_attr_readonly).num = 57
+    getattr(outer, m_attr_readwrite).num = 57
+    assert field.num == 57
+    del field
+    assert getattr(outer, m_attr_readonly).num == 57
+    assert getattr(outer, m_attr_readwrite).num == 57
+
+
+@pytest.mark.parametrize(
+    ("field_type", "num_default", "outer_type"),
+    [
+        (m.ClassicField, -88, m.ClassicOuter),
+        (m.Field, -99, m.Outer),
+    ],
+)
+@pytest.mark.parametrize("m_attr", ["m_mptr", "m_cptr"])
+@pytest.mark.parametrize("r_kind", ["_readonly", "_readwrite"])
+def test_ptr(field_type, num_default, outer_type, m_attr, r_kind):
+    m_attr_r_kind = m_attr + r_kind
+    outer = outer_type()
+    assert getattr(outer, m_attr_r_kind) is None
+    field = field_type()
+    assert field.num == num_default
+    setattr(outer, m_attr + "_readwrite", field)
+    assert getattr(outer, m_attr_r_kind).num == num_default
+    field.num = 76
+    assert getattr(outer, m_attr_r_kind).num == 76
+    # Change to -88 or -99 to demonstrate Undefined Behavior (dangling pointer).
+    if num_default == 88 and m_attr == "m_mptr":
+        del field
+    assert getattr(outer, m_attr_r_kind).num == 76
+
+
+@pytest.mark.parametrize("m_attr_readwrite", ["m_uqmp_readwrite", "m_uqcp_readwrite"])
+def test_uqp(m_attr_readwrite):
+    outer = m.Outer()
+    assert getattr(outer, m_attr_readwrite) is None
+    field_orig = m.Field()
+    field_orig.num = 39
+    setattr(outer, m_attr_readwrite, field_orig)
+    with pytest.raises(ValueError, match="Python instance was disowned"):
+        _ = field_orig.num
+    field_retr1 = getattr(outer, m_attr_readwrite)
+    assert getattr(outer, m_attr_readwrite) is None
+    assert field_retr1.num == 39
+    field_retr1.num = 93
+    setattr(outer, m_attr_readwrite, field_retr1)
+    with pytest.raises(ValueError):
+        _ = field_retr1.num
+    field_retr2 = getattr(outer, m_attr_readwrite)
+    assert field_retr2.num == 93
+
+
+# Proof-of-concept (POC) for safe & intuitive Python access to unique_ptr members.
+# The C++ member unique_ptr is disowned to a temporary Python object for accessing
+# an attribute of the member. After the attribute was accessed, the Python object
+# is disowned back to the C++ member unique_ptr.
+# Productizing this POC is left for a future separate PR, as needed.
+class unique_ptr_field_proxy_poc:
+    def __init__(self, obj, field_name):
+        object.__setattr__(self, "__obj", obj)
+        object.__setattr__(self, "__field_name", field_name)
+
+    def __getattr__(self, *args, **kwargs):
+        return _proxy_dereference(self, getattr, *args, **kwargs)
+
+    def __setattr__(self, *args, **kwargs):
+        return _proxy_dereference(self, setattr, *args, **kwargs)
+
+    def __delattr__(self, *args, **kwargs):
+        return _proxy_dereference(self, delattr, *args, **kwargs)
+
+
+def _proxy_dereference(proxy, xxxattr, *args, **kwargs):
+    obj = object.__getattribute__(proxy, "__obj")
+    field_name = object.__getattribute__(proxy, "__field_name")
+    field = getattr(obj, field_name)  # Disowns the C++ unique_ptr member.
+    assert field is not None
+    try:
+        return xxxattr(field, *args, **kwargs)
+    finally:
+        setattr(obj, field_name, field)  # Disowns the temporary Python object (field).
+
+
+@pytest.mark.parametrize("m_attr", ["m_uqmp", "m_uqcp"])
+def test_unique_ptr_field_proxy_poc(m_attr):
+    m_attr_readwrite = m_attr + "_readwrite"
+    outer = m.Outer()
+    field_orig = m.Field()
+    field_orig.num = 45
+    setattr(outer, m_attr_readwrite, field_orig)
+    field_proxy = unique_ptr_field_proxy_poc(outer, m_attr_readwrite)
+    assert field_proxy.num == 45
+    assert field_proxy.num == 45
+    with pytest.raises(AttributeError):
+        _ = field_proxy.xyz
+    assert field_proxy.num == 45
+    field_proxy.num = 82
+    assert field_proxy.num == 82
+    field_proxy = unique_ptr_field_proxy_poc(outer, m_attr_readwrite)
+    assert field_proxy.num == 82
+    with pytest.raises(AttributeError):
+        del field_proxy.num
+    assert field_proxy.num == 82
+
+
+def test_readonly_char6_member():
+    obj = m.WithCharArrayMember()
+    assert obj.char6_member == "Char6"
+
+
+def test_readonly_const_char_ptr_member():
+    obj = m.WithConstCharPtrMember()
+    assert obj.const_char_ptr_member == "ConstChar*"
diff --git a/tests/test_class_sh_property_non_owning.cpp b/tests/test_class_sh_property_non_owning.cpp
new file mode 100644
index 0000000..87b8433
--- /dev/null
+++ b/tests/test_class_sh_property_non_owning.cpp
@@ -0,0 +1,75 @@
+#include "pybind11/smart_holder.h"
+#include "pybind11_tests.h"
+
+#include <cstddef>
+#include <vector>
+
+namespace test_class_sh_property_non_owning {
+
+struct CoreField {
+    explicit CoreField(int int_value = -99) : int_value{int_value} {}
+    int int_value;
+};
+
+struct DataField {
+    DataField(int i_value, int i_shared, int i_unique)
+        : core_fld_value{i_value}, core_fld_shared_ptr{new CoreField{i_shared}},
+          core_fld_raw_ptr{core_fld_shared_ptr.get()},
+          core_fld_unique_ptr{new CoreField{i_unique}} {}
+    CoreField core_fld_value;
+    std::shared_ptr<CoreField> core_fld_shared_ptr;
+    CoreField *core_fld_raw_ptr;
+    std::unique_ptr<CoreField> core_fld_unique_ptr;
+};
+
+struct DataFieldsHolder {
+private:
+    std::vector<DataField> vec;
+
+public:
+    explicit DataFieldsHolder(std::size_t vec_size) {
+        for (std::size_t i = 0; i < vec_size; i++) {
+            int i11 = static_cast<int>(i) * 11;
+            vec.emplace_back(13 + i11, 14 + i11, 15 + i11);
+        }
+    }
+
+    DataField *vec_at(std::size_t index) {
+        if (index >= vec.size()) {
+            return nullptr;
+        }
+        return &vec[index];
+    }
+};
+
+} // namespace test_class_sh_property_non_owning
+
+using namespace test_class_sh_property_non_owning;
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(CoreField)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(DataField)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(DataFieldsHolder)
+
+TEST_SUBMODULE(class_sh_property_non_owning, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    py::classh<CoreField>(m, "CoreField").def_readwrite("int_value", &CoreField::int_value);
+
+    py::classh<DataField>(m, "DataField")
+        .def_readonly("core_fld_value_ro", &DataField::core_fld_value)
+        .def_readwrite("core_fld_value_rw", &DataField::core_fld_value)
+        .def_readonly("core_fld_shared_ptr_ro", &DataField::core_fld_shared_ptr)
+        .def_readwrite("core_fld_shared_ptr_rw", &DataField::core_fld_shared_ptr)
+        .def_readonly("core_fld_raw_ptr_ro", &DataField::core_fld_raw_ptr)
+        .def_readwrite("core_fld_raw_ptr_rw", &DataField::core_fld_raw_ptr)
+        .def_readwrite("core_fld_unique_ptr_rw", &DataField::core_fld_unique_ptr);
+
+    py::classh<DataFieldsHolder>(m, "DataFieldsHolder")
+        .def(py::init<std::size_t>())
+        .def("vec_at", &DataFieldsHolder::vec_at, py::return_value_policy::reference_internal);
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_property_non_owning.py b/tests/test_class_sh_property_non_owning.py
new file mode 100644
index 0000000..b977e75
--- /dev/null
+++ b/tests/test_class_sh_property_non_owning.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_property_non_owning as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+@pytest.mark.parametrize("persistent_holder", [True, False])
+@pytest.mark.parametrize(
+    ("core_fld", "expected"),
+    [
+        ("core_fld_value_ro", (13, 24)),
+        ("core_fld_value_rw", (13, 24)),
+        ("core_fld_shared_ptr_ro", (14, 25)),
+        ("core_fld_shared_ptr_rw", (14, 25)),
+        ("core_fld_raw_ptr_ro", (14, 25)),
+        ("core_fld_raw_ptr_rw", (14, 25)),
+        ("core_fld_unique_ptr_rw", (15, 26)),
+    ],
+)
+def test_core_fld_common(core_fld, expected, persistent_holder):
+    if persistent_holder:
+        h = m.DataFieldsHolder(2)
+        for i, exp in enumerate(expected):
+            c = getattr(h.vec_at(i), core_fld)
+            assert c.int_value == exp
+    else:
+        for i, exp in enumerate(expected):
+            c = getattr(m.DataFieldsHolder(2).vec_at(i), core_fld)
+            assert c.int_value == exp
diff --git a/tests/test_class_sh_shared_ptr_copy_move.cpp b/tests/test_class_sh_shared_ptr_copy_move.cpp
new file mode 100644
index 0000000..9998327
--- /dev/null
+++ b/tests/test_class_sh_shared_ptr_copy_move.cpp
@@ -0,0 +1,119 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace pybind11_tests {
+namespace {
+
+const std::string fooNames[] = {"ShPtr_", "SmHld_"};
+
+template <int SerNo>
+struct Foo {
+    std::string history;
+    explicit Foo(const std::string &history_) : history(history_) {}
+    Foo(const Foo &other) : history(other.history + "_CpCtor") {}
+    Foo(Foo &&other) noexcept : history(other.history + "_MvCtor") {}
+    Foo &operator=(const Foo &other) {
+        history = other.history + "_OpEqLv";
+        return *this;
+    }
+    Foo &operator=(Foo &&other) noexcept {
+        history = other.history + "_OpEqRv";
+        return *this;
+    }
+    std::string get_history() const { return "Foo" + fooNames[SerNo] + history; }
+};
+
+using FooShPtr = Foo<0>;
+using FooSmHld = Foo<1>;
+
+struct Outer {
+    std::shared_ptr<FooShPtr> ShPtr;
+    std::shared_ptr<FooSmHld> SmHld;
+    Outer()
+        : ShPtr(std::make_shared<FooShPtr>("Outer")), SmHld(std::make_shared<FooSmHld>("Outer")) {}
+    std::shared_ptr<FooShPtr> getShPtr() const { return ShPtr; }
+    std::shared_ptr<FooSmHld> getSmHld() const { return SmHld; }
+};
+
+} // namespace
+} // namespace pybind11_tests
+
+PYBIND11_TYPE_CASTER_BASE_HOLDER(pybind11_tests::FooShPtr,
+                                 std::shared_ptr<pybind11_tests::FooShPtr>)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::FooSmHld)
+
+namespace pybind11_tests {
+
+TEST_SUBMODULE(class_sh_shared_ptr_copy_move, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    namespace py = pybind11;
+
+    py::class_<FooShPtr, std::shared_ptr<FooShPtr>>(m, "FooShPtr")
+        .def("get_history", &FooShPtr::get_history);
+    py::classh<FooSmHld>(m, "FooSmHld").def("get_history", &FooSmHld::get_history);
+
+    auto outer = py::class_<Outer>(m, "Outer").def(py::init());
+#    define MAKE_PROP(PropTyp)                                                                    \
+        MAKE_PROP_FOO(ShPtr, PropTyp)                                                             \
+        MAKE_PROP_FOO(SmHld, PropTyp)
+
+#    define MAKE_PROP_FOO(FooTyp, PropTyp)                                                        \
+        .def_##PropTyp(#FooTyp "_" #PropTyp "_default", &Outer::FooTyp)                           \
+            .def_##PropTyp(                                                                       \
+                #FooTyp "_" #PropTyp "_copy", &Outer::FooTyp, py::return_value_policy::copy)      \
+            .def_##PropTyp(                                                                       \
+                #FooTyp "_" #PropTyp "_move", &Outer::FooTyp, py::return_value_policy::move)
+    outer MAKE_PROP(readonly) MAKE_PROP(readwrite);
+#    undef MAKE_PROP_FOO
+
+#    define MAKE_PROP_FOO(FooTyp, PropTyp)                                                        \
+        .def_##PropTyp(#FooTyp "_property_" #PropTyp "_default", &Outer::FooTyp)                  \
+            .def_property_##PropTyp(#FooTyp "_property_" #PropTyp "_copy",                        \
+                                    &Outer::get##FooTyp,                                          \
+                                    py::return_value_policy::copy)                                \
+            .def_property_##PropTyp(#FooTyp "_property_" #PropTyp "_move",                        \
+                                    &Outer::get##FooTyp,                                          \
+                                    py::return_value_policy::move)
+    outer MAKE_PROP(readonly);
+#    undef MAKE_PROP_FOO
+#    undef MAKE_PROP
+
+    m.def("test_ShPtr_copy", []() {
+        auto o = std::make_shared<FooShPtr>("copy");
+        auto l = py::list();
+        l.append(o);
+        return l;
+    });
+    m.def("test_SmHld_copy", []() {
+        auto o = std::make_shared<FooSmHld>("copy");
+        auto l = py::list();
+        l.append(o);
+        return l;
+    });
+
+    m.def("test_ShPtr_move", []() {
+        auto o = std::make_shared<FooShPtr>("move");
+        auto l = py::list();
+        l.append(std::move(o));
+        return l;
+    });
+    m.def("test_SmHld_move", []() {
+        auto o = std::make_shared<FooSmHld>("move");
+        auto l = py::list();
+        l.append(std::move(o));
+        return l;
+    });
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
+
+} // namespace pybind11_tests
diff --git a/tests/test_class_sh_shared_ptr_copy_move.py b/tests/test_class_sh_shared_ptr_copy_move.py
new file mode 100644
index 0000000..70f5c76
--- /dev/null
+++ b/tests/test_class_sh_shared_ptr_copy_move.py
@@ -0,0 +1,46 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_shared_ptr_copy_move as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+def test_shptr_copy():
+    txt = m.test_ShPtr_copy()[0].get_history()
+    assert txt == "FooShPtr_copy"
+
+
+def test_smhld_copy():
+    txt = m.test_SmHld_copy()[0].get_history()
+    assert txt == "FooSmHld_copy"
+
+
+def test_shptr_move():
+    txt = m.test_ShPtr_move()[0].get_history()
+    assert txt == "FooShPtr_move"
+
+
+def test_smhld_move():
+    txt = m.test_SmHld_move()[0].get_history()
+    assert txt == "FooSmHld_move"
+
+
+def _check_property(foo_typ, prop_typ, policy):
+    o = m.Outer()
+    name = f"{foo_typ}_{prop_typ}_{policy}"
+    history = f"Foo{foo_typ}_Outer"
+    f = getattr(o, name)
+    assert f.get_history() == history
+    # and try again to check that o did not get changed
+    f = getattr(o, name)
+    assert f.get_history() == history
+
+
+def test_properties():
+    for prop_typ in ("readonly", "readwrite", "property_readonly"):
+        for foo_typ in ("ShPtr", "SmHld"):
+            for policy in ("default", "copy", "move"):
+                _check_property(foo_typ, prop_typ, policy)
diff --git a/tests/test_class_sh_trampoline_basic.cpp b/tests/test_class_sh_trampoline_basic.cpp
new file mode 100644
index 0000000..32bf23d
--- /dev/null
+++ b/tests/test_class_sh_trampoline_basic.cpp
@@ -0,0 +1,98 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+
+namespace pybind11_tests {
+namespace class_sh_trampoline_basic {
+
+template <int SerNo> // Using int as a trick to easily generate a series of types.
+struct Abase {
+    int val = 0;
+    virtual ~Abase() = default;
+    explicit Abase(int val_) : val{val_} {}
+    int Get() const { return val * 10 + 3; }
+    virtual int Add(int other_val) const = 0;
+
+    // Some compilers complain about implicitly defined versions of some of the following:
+    Abase(const Abase &) = default;
+    Abase(Abase &&) noexcept = default;
+    Abase &operator=(const Abase &) = default;
+    Abase &operator=(Abase &&) noexcept = default;
+};
+
+template <int SerNo>
+struct AbaseAlias : Abase<SerNo> {
+    using Abase<SerNo>::Abase;
+
+    int Add(int other_val) const override {
+        PYBIND11_OVERRIDE_PURE(int,          /* Return type */
+                               Abase<SerNo>, /* Parent class */
+                               Add,          /* Name of function in C++ (must match Python name) */
+                               other_val);
+    }
+};
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+template <>
+struct AbaseAlias<1> : Abase<1>, py::trampoline_self_life_support {
+    using Abase<1>::Abase;
+
+    int Add(int other_val) const override {
+        PYBIND11_OVERRIDE_PURE(int,      /* Return type */
+                               Abase<1>, /* Parent class */
+                               Add,      /* Name of function in C++ (must match Python name) */
+                               other_val);
+    }
+};
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+
+template <int SerNo>
+int AddInCppRawPtr(const Abase<SerNo> *obj, int other_val) {
+    return obj->Add(other_val) * 10 + 7;
+}
+
+template <int SerNo>
+int AddInCppSharedPtr(std::shared_ptr<Abase<SerNo>> obj, int other_val) {
+    return obj->Add(other_val) * 100 + 11;
+}
+
+template <int SerNo>
+int AddInCppUniquePtr(std::unique_ptr<Abase<SerNo>> obj, int other_val) {
+    return obj->Add(other_val) * 100 + 13;
+}
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+template <int SerNo>
+void wrap(py::module_ m, const char *py_class_name) {
+    py::classh<Abase<SerNo>, AbaseAlias<SerNo>>(m, py_class_name)
+        .def(py::init<int>(), py::arg("val"))
+        .def("Get", &Abase<SerNo>::Get)
+        .def("Add", &Abase<SerNo>::Add, py::arg("other_val"));
+
+    m.def("AddInCppRawPtr", AddInCppRawPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
+    m.def("AddInCppSharedPtr", AddInCppSharedPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
+    m.def("AddInCppUniquePtr", AddInCppUniquePtr<SerNo>, py::arg("obj"), py::arg("other_val"));
+}
+#endif
+
+} // namespace class_sh_trampoline_basic
+} // namespace pybind11_tests
+
+using namespace pybind11_tests::class_sh_trampoline_basic;
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(Abase<0>)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(Abase<1>)
+
+TEST_SUBMODULE(class_sh_trampoline_basic, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    wrap<0>(m, "Abase0");
+    wrap<1>(m, "Abase1");
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_trampoline_basic.py b/tests/test_class_sh_trampoline_basic.py
new file mode 100644
index 0000000..935a172
--- /dev/null
+++ b/tests/test_class_sh_trampoline_basic.py
@@ -0,0 +1,62 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_trampoline_basic as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+class PyDrvd0(m.Abase0):
+    def __init__(self, val):
+        super().__init__(val)
+
+    def Add(self, other_val):
+        return self.Get() * 100 + other_val
+
+
+class PyDrvd1(m.Abase1):
+    def __init__(self, val):
+        super().__init__(val)
+
+    def Add(self, other_val):
+        return self.Get() * 200 + other_val
+
+
+def test_drvd0_add():
+    drvd = PyDrvd0(74)
+    assert drvd.Add(38) == (74 * 10 + 3) * 100 + 38
+
+
+def test_drvd0_add_in_cpp_raw_ptr():
+    drvd = PyDrvd0(52)
+    assert m.AddInCppRawPtr(drvd, 27) == ((52 * 10 + 3) * 100 + 27) * 10 + 7
+
+
+def test_drvd0_add_in_cpp_shared_ptr():
+    while True:
+        drvd = PyDrvd0(36)
+        assert m.AddInCppSharedPtr(drvd, 56) == ((36 * 10 + 3) * 100 + 56) * 100 + 11
+        return  # Comment out for manual leak checking (use `top` command).
+
+
+def test_drvd0_add_in_cpp_unique_ptr():
+    while True:
+        drvd = PyDrvd0(0)
+        with pytest.raises(ValueError) as exc_info:
+            m.AddInCppUniquePtr(drvd, 0)
+        assert (
+            str(exc_info.value)
+            == "Alias class (also known as trampoline) does not inherit from"
+            " py::trampoline_self_life_support, therefore the ownership of this"
+            " instance cannot safely be transferred to C++."
+        )
+        return  # Comment out for manual leak checking (use `top` command).
+
+
+def test_drvd1_add_in_cpp_unique_ptr():
+    while True:
+        drvd = PyDrvd1(25)
+        assert m.AddInCppUniquePtr(drvd, 83) == ((25 * 10 + 3) * 200 + 83) * 100 + 13
+        return  # Comment out for manual leak checking (use `top` command).
diff --git a/tests/test_class_sh_trampoline_self_life_support.cpp b/tests/test_class_sh_trampoline_self_life_support.cpp
new file mode 100644
index 0000000..c342598
--- /dev/null
+++ b/tests/test_class_sh_trampoline_self_life_support.cpp
@@ -0,0 +1,98 @@
+// Copyright (c) 2021 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "pybind11/smart_holder.h"
+#include "pybind11/trampoline_self_life_support.h"
+#include "pybind11_tests.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace pybind11_tests {
+namespace class_sh_trampoline_self_life_support {
+
+struct Big5 { // Also known as "rule of five".
+    std::string history;
+
+    explicit Big5(std::string history_start) : history{std::move(history_start)} {}
+
+    Big5(const Big5 &other) { history = other.history + "_CpCtor"; }
+
+    Big5(Big5 &&other) noexcept { history = other.history + "_MvCtor"; }
+
+    Big5 &operator=(const Big5 &other) {
+        history = other.history + "_OpEqLv";
+        return *this;
+    }
+
+    Big5 &operator=(Big5 &&other) noexcept {
+        history = other.history + "_OpEqRv";
+        return *this;
+    }
+
+    virtual ~Big5() = default;
+
+protected:
+    Big5() : history{"DefaultConstructor"} {}
+};
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+struct Big5Trampoline : Big5, py::trampoline_self_life_support {
+    using Big5::Big5;
+};
+#endif
+
+} // namespace class_sh_trampoline_self_life_support
+} // namespace pybind11_tests
+
+using namespace pybind11_tests::class_sh_trampoline_self_life_support;
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(Big5)
+
+TEST_SUBMODULE(class_sh_trampoline_self_life_support, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    py::classh<Big5, Big5Trampoline>(m, "Big5")
+        .def(py::init<std::string>())
+        .def_readonly("history", &Big5::history);
+
+    m.def("action", [](std::unique_ptr<Big5> obj, int action_id) {
+        py::object o2 = py::none();
+        // This is very unusual, but needed to directly exercise the trampoline_self_life_support
+        // CpCtor, MvCtor, operator= lvalue, operator= rvalue.
+        auto *obj_trampoline = dynamic_cast<Big5Trampoline *>(obj.get());
+        if (obj_trampoline != nullptr) {
+            switch (action_id) {
+                case 0: { // CpCtor
+                    std::unique_ptr<Big5> cp(new Big5Trampoline(*obj_trampoline));
+                    o2 = py::cast(std::move(cp));
+                } break;
+                case 1: { // MvCtor
+                    std::unique_ptr<Big5> mv(new Big5Trampoline(std::move(*obj_trampoline)));
+                    o2 = py::cast(std::move(mv));
+                } break;
+                case 2: { // operator= lvalue
+                    std::unique_ptr<Big5> lv(new Big5Trampoline);
+                    *lv = *obj_trampoline; // NOLINT clang-tidy cppcoreguidelines-slicing
+                    o2 = py::cast(std::move(lv));
+                } break;
+                case 3: { // operator= rvalue
+                    std::unique_ptr<Big5> rv(new Big5Trampoline);
+                    *rv = std::move(*obj_trampoline);
+                    o2 = py::cast(std::move(rv));
+                } break;
+                default:
+                    break;
+            }
+        }
+        py::object o1 = py::cast(std::move(obj));
+        return py::make_tuple(o1, o2);
+    });
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_trampoline_self_life_support.py b/tests/test_class_sh_trampoline_self_life_support.py
new file mode 100644
index 0000000..076f300
--- /dev/null
+++ b/tests/test_class_sh_trampoline_self_life_support.py
@@ -0,0 +1,41 @@
+from __future__ import annotations
+
+import pytest
+
+import pybind11_tests.class_sh_trampoline_self_life_support as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+class PyBig5(m.Big5):
+    pass
+
+
+def test_m_big5():
+    obj = m.Big5("Seed")
+    assert obj.history == "Seed"
+    o1, o2 = m.action(obj, 0)
+    assert o1 is not obj
+    assert o1.history == "Seed"
+    with pytest.raises(ValueError) as excinfo:
+        _ = obj.history
+    assert "Python instance was disowned" in str(excinfo.value)
+    assert o2 is None
+
+
+@pytest.mark.parametrize(
+    ("action_id", "expected_history"),
+    [
+        (0, "Seed_CpCtor"),
+        (1, "Seed_MvCtor"),
+        (2, "Seed_OpEqLv"),
+        (3, "Seed_OpEqRv"),
+    ],
+)
+def test_py_big5(action_id, expected_history):
+    obj = PyBig5("Seed")
+    assert obj.history == "Seed"
+    o1, o2 = m.action(obj, action_id)
+    assert o1 is obj
+    assert o2.history == expected_history
diff --git a/tests/test_class_sh_trampoline_shared_from_this.cpp b/tests/test_class_sh_trampoline_shared_from_this.cpp
new file mode 100644
index 0000000..d09fe00
--- /dev/null
+++ b/tests/test_class_sh_trampoline_shared_from_this.cpp
@@ -0,0 +1,150 @@
+// Copyright (c) 2021 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "pybind11/smart_holder.h"
+#include "pybind11_tests.h"
+
+#include <memory>
+#include <string>
+
+namespace pybind11_tests {
+namespace class_sh_trampoline_shared_from_this {
+
+struct Sft : std::enable_shared_from_this<Sft> {
+    std::string history;
+    explicit Sft(const std::string &history_seed) : history{history_seed} {}
+    virtual ~Sft() = default;
+
+#if defined(__clang__)
+    // "Group of 4" begin.
+    // This group is not meant to be used, but will leave a trace in the
+    // history in case something goes wrong.
+    // However, compilers other than clang have a variety of issues. It is not
+    // worth the trouble covering all platforms.
+    Sft(const Sft &other) : enable_shared_from_this(other) { history = other.history + "_CpCtor"; }
+
+    Sft(Sft &&other) noexcept { history = other.history + "_MvCtor"; }
+
+    Sft &operator=(const Sft &other) {
+        history = other.history + "_OpEqLv";
+        return *this;
+    }
+
+    Sft &operator=(Sft &&other) noexcept {
+        history = other.history + "_OpEqRv";
+        return *this;
+    }
+    // "Group of 4" end.
+#endif
+};
+
+struct SftSharedPtrStash {
+    int ser_no;
+    std::vector<std::shared_ptr<Sft>> stash;
+    explicit SftSharedPtrStash(int ser_no) : ser_no{ser_no} {}
+    void Clear() { stash.clear(); }
+    void Add(const std::shared_ptr<Sft> &obj) {
+        if (!obj->history.empty()) {
+            obj->history += "_Stash" + std::to_string(ser_no) + "Add";
+        }
+        stash.push_back(obj);
+    }
+    void AddSharedFromThis(Sft *obj) {
+        auto sft = obj->shared_from_this();
+        if (!sft->history.empty()) {
+            sft->history += "_Stash" + std::to_string(ser_no) + "AddSharedFromThis";
+        }
+        stash.push_back(sft);
+    }
+    std::string history(unsigned i) {
+        if (i < stash.size()) {
+            return stash[i]->history;
+        }
+        return "OutOfRange";
+    }
+    long use_count(unsigned i) {
+        if (i < stash.size()) {
+            return stash[i].use_count();
+        }
+        return -1;
+    }
+};
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+struct SftTrampoline : Sft, py::trampoline_self_life_support {
+    using Sft::Sft;
+};
+#endif
+
+long use_count(const std::shared_ptr<Sft> &obj) { return obj.use_count(); }
+
+long pass_shared_ptr(const std::shared_ptr<Sft> &obj) {
+    auto sft = obj->shared_from_this();
+    if (!sft->history.empty()) {
+        sft->history += "_PassSharedPtr";
+    }
+    return sft.use_count();
+}
+
+std::string pass_unique_ptr_cref(const std::unique_ptr<Sft> &obj) {
+    return obj ? obj->history : "<NULLPTR>";
+}
+void pass_unique_ptr_rref(std::unique_ptr<Sft> &&) {
+    throw std::runtime_error("Expected to not be reached.");
+}
+
+Sft *make_pure_cpp_sft_raw_ptr(const std::string &history_seed) { return new Sft{history_seed}; }
+
+std::unique_ptr<Sft> make_pure_cpp_sft_unq_ptr(const std::string &history_seed) {
+    return std::unique_ptr<Sft>(new Sft{history_seed});
+}
+
+std::shared_ptr<Sft> make_pure_cpp_sft_shd_ptr(const std::string &history_seed) {
+    return std::make_shared<Sft>(history_seed);
+}
+
+std::shared_ptr<Sft> pass_through_shd_ptr(const std::shared_ptr<Sft> &obj) { return obj; }
+
+} // namespace class_sh_trampoline_shared_from_this
+} // namespace pybind11_tests
+
+using namespace pybind11_tests::class_sh_trampoline_shared_from_this;
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(Sft)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(SftSharedPtrStash)
+
+TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    py::classh<Sft, SftTrampoline>(m, "Sft")
+        .def(py::init<const std::string &>())
+        .def(py::init([](const std::string &history, int) {
+            return std::make_shared<SftTrampoline>(history);
+        }))
+        .def_readonly("history", &Sft::history)
+        // This leads to multiple entries in registered_instances:
+        .def(py::init([](const std::shared_ptr<Sft> &existing) { return existing; }));
+
+    py::classh<SftSharedPtrStash>(m, "SftSharedPtrStash")
+        .def(py::init<int>())
+        .def("Clear", &SftSharedPtrStash::Clear)
+        .def("Add", &SftSharedPtrStash::Add)
+        .def("AddSharedFromThis", &SftSharedPtrStash::AddSharedFromThis)
+        .def("history", &SftSharedPtrStash::history)
+        .def("use_count", &SftSharedPtrStash::use_count);
+
+    m.def("use_count", use_count);
+    m.def("pass_shared_ptr", pass_shared_ptr);
+    m.def("pass_unique_ptr_cref", pass_unique_ptr_cref);
+    m.def("pass_unique_ptr_rref", pass_unique_ptr_rref);
+    m.def("make_pure_cpp_sft_raw_ptr", make_pure_cpp_sft_raw_ptr);
+    m.def("make_pure_cpp_sft_unq_ptr", make_pure_cpp_sft_unq_ptr);
+    m.def("make_pure_cpp_sft_shd_ptr", make_pure_cpp_sft_shd_ptr);
+    m.def("pass_through_shd_ptr", pass_through_shd_ptr);
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_trampoline_shared_from_this.py b/tests/test_class_sh_trampoline_shared_from_this.py
new file mode 100644
index 0000000..9b0d122
--- /dev/null
+++ b/tests/test_class_sh_trampoline_shared_from_this.py
@@ -0,0 +1,250 @@
+from __future__ import annotations
+
+import sys
+import weakref
+
+import pytest
+
+import env
+import pybind11_tests.class_sh_trampoline_shared_from_this as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+class PySft(m.Sft):
+    pass
+
+
+def test_release_and_shared_from_this():
+    # Exercises the most direct path from building a shared_from_this-visible
+    # shared_ptr to calling shared_from_this.
+    obj = PySft("PySft")
+    assert obj.history == "PySft"
+    assert m.use_count(obj) == 1
+    assert m.pass_shared_ptr(obj) == 2
+    assert obj.history == "PySft_PassSharedPtr"
+    assert m.use_count(obj) == 1
+    assert m.pass_shared_ptr(obj) == 2
+    assert obj.history == "PySft_PassSharedPtr_PassSharedPtr"
+    assert m.use_count(obj) == 1
+
+
+def test_release_and_shared_from_this_leak():
+    obj = PySft("")
+    while True:
+        m.pass_shared_ptr(obj)
+        assert not obj.history
+        assert m.use_count(obj) == 1
+        break  # Comment out for manual leak checking (use `top` command).
+
+
+def test_release_and_stash():
+    # Exercises correct functioning of guarded_delete weak_ptr.
+    obj = PySft("PySft")
+    stash1 = m.SftSharedPtrStash(1)
+    stash1.Add(obj)
+    exp_hist = "PySft_Stash1Add"
+    assert obj.history == exp_hist
+    assert m.use_count(obj) == 2
+    assert stash1.history(0) == exp_hist
+    assert stash1.use_count(0) == 1
+    assert m.pass_shared_ptr(obj) == 3
+    exp_hist += "_PassSharedPtr"
+    assert obj.history == exp_hist
+    assert m.use_count(obj) == 2
+    assert stash1.history(0) == exp_hist
+    assert stash1.use_count(0) == 1
+    stash2 = m.SftSharedPtrStash(2)
+    stash2.Add(obj)
+    exp_hist += "_Stash2Add"
+    assert obj.history == exp_hist
+    assert m.use_count(obj) == 3
+    assert stash2.history(0) == exp_hist
+    assert stash2.use_count(0) == 2
+    stash2.Add(obj)
+    exp_hist += "_Stash2Add"
+    assert obj.history == exp_hist
+    assert m.use_count(obj) == 4
+    assert stash1.history(0) == exp_hist
+    assert stash1.use_count(0) == 3
+    assert stash2.history(0) == exp_hist
+    assert stash2.use_count(0) == 3
+    assert stash2.history(1) == exp_hist
+    assert stash2.use_count(1) == 3
+    del obj
+    assert stash2.history(0) == exp_hist
+    assert stash2.use_count(0) == 3
+    assert stash2.history(1) == exp_hist
+    assert stash2.use_count(1) == 3
+    stash2.Clear()
+    assert stash1.history(0) == exp_hist
+    assert stash1.use_count(0) == 1
+
+
+def test_release_and_stash_leak():
+    obj = PySft("")
+    while True:
+        stash1 = m.SftSharedPtrStash(1)
+        stash1.Add(obj)
+        assert not obj.history
+        assert m.use_count(obj) == 2
+        assert stash1.use_count(0) == 1
+        stash1.Add(obj)
+        assert not obj.history
+        assert m.use_count(obj) == 3
+        assert stash1.use_count(0) == 2
+        assert stash1.use_count(1) == 2
+        break  # Comment out for manual leak checking (use `top` command).
+
+
+def test_release_and_stash_via_shared_from_this():
+    # Exercises that the smart_holder vptr is invisible to the shared_from_this mechanism.
+    obj = PySft("PySft")
+    stash1 = m.SftSharedPtrStash(1)
+    with pytest.raises(RuntimeError) as exc_info:
+        stash1.AddSharedFromThis(obj)
+    assert str(exc_info.value) == "bad_weak_ptr"
+    stash1.Add(obj)
+    assert obj.history == "PySft_Stash1Add"
+    assert stash1.use_count(0) == 1
+    stash1.AddSharedFromThis(obj)
+    assert obj.history == "PySft_Stash1Add_Stash1AddSharedFromThis"
+    assert stash1.use_count(0) == 2
+    assert stash1.use_count(1) == 2
+
+
+def test_release_and_stash_via_shared_from_this_leak():
+    obj = PySft("")
+    while True:
+        stash1 = m.SftSharedPtrStash(1)
+        with pytest.raises(RuntimeError) as exc_info:
+            stash1.AddSharedFromThis(obj)
+        assert str(exc_info.value) == "bad_weak_ptr"
+        stash1.Add(obj)
+        assert not obj.history
+        assert stash1.use_count(0) == 1
+        stash1.AddSharedFromThis(obj)
+        assert not obj.history
+        assert stash1.use_count(0) == 2
+        assert stash1.use_count(1) == 2
+        break  # Comment out for manual leak checking (use `top` command).
+
+
+def test_pass_released_shared_ptr_as_unique_ptr():
+    # Exercises that returning a unique_ptr fails while a shared_from_this
+    # visible shared_ptr exists.
+    obj = PySft("PySft")
+    stash1 = m.SftSharedPtrStash(1)
+    stash1.Add(obj)  # Releases shared_ptr to C++.
+    assert m.pass_unique_ptr_cref(obj) == "PySft_Stash1Add"
+    assert obj.history == "PySft_Stash1Add"
+    with pytest.raises(ValueError) as exc_info:
+        m.pass_unique_ptr_rref(obj)
+    assert str(exc_info.value) == (
+        "Python instance is currently owned by a std::shared_ptr."
+    )
+    assert obj.history == "PySft_Stash1Add"
+
+
+@pytest.mark.parametrize(
+    "make_f",
+    [
+        m.make_pure_cpp_sft_raw_ptr,
+        m.make_pure_cpp_sft_unq_ptr,
+        m.make_pure_cpp_sft_shd_ptr,
+    ],
+)
+def test_pure_cpp_sft_raw_ptr(make_f):
+    # Exercises void_cast_raw_ptr logic for different situations.
+    obj = make_f("PureCppSft")
+    assert m.pass_shared_ptr(obj) == 3
+    assert obj.history == "PureCppSft_PassSharedPtr"
+    obj = make_f("PureCppSft")
+    stash1 = m.SftSharedPtrStash(1)
+    stash1.AddSharedFromThis(obj)
+    assert obj.history == "PureCppSft_Stash1AddSharedFromThis"
+
+
+def test_multiple_registered_instances_for_same_pointee():
+    obj0 = PySft("PySft")
+    obj0.attachment_in_dict = "Obj0"
+    assert m.pass_through_shd_ptr(obj0) is obj0
+    while True:
+        obj = m.Sft(obj0)
+        assert obj is not obj0
+        obj_pt = m.pass_through_shd_ptr(obj)
+        # Unpredictable! Because registered_instances is as std::unordered_multimap.
+        assert obj_pt is obj0 or obj_pt is obj
+        # Multiple registered_instances for the same pointee can lead to unpredictable results:
+        if obj_pt is obj0:
+            assert obj_pt.attachment_in_dict == "Obj0"
+        else:
+            assert not hasattr(obj_pt, "attachment_in_dict")
+        assert obj0.history == "PySft"
+        break  # Comment out for manual leak checking (use `top` command).
+
+
+def test_multiple_registered_instances_for_same_pointee_leak():
+    obj0 = PySft("")
+    while True:
+        stash1 = m.SftSharedPtrStash(1)
+        stash1.Add(m.Sft(obj0))
+        assert stash1.use_count(0) == 1
+        stash1.Add(m.Sft(obj0))
+        assert stash1.use_count(0) == 1
+        assert stash1.use_count(1) == 1
+        assert not obj0.history
+        break  # Comment out for manual leak checking (use `top` command).
+
+
+def test_multiple_registered_instances_for_same_pointee_recursive():
+    while True:
+        obj0 = PySft("PySft")
+        if not env.PYPY:
+            obj0_wr = weakref.ref(obj0)
+        obj = obj0
+        # This loop creates a chain of instances linked by shared_ptrs.
+        for _ in range(10):
+            obj_next = m.Sft(obj)
+            assert obj_next is not obj
+            obj = obj_next
+            del obj_next
+            assert obj.history == "PySft"
+        del obj0
+        if not env.PYPY and not env.GRAALPY:
+            assert obj0_wr() is not None
+        del obj  # This releases the chain recursively.
+        if not env.PYPY and not env.GRAALPY:
+            assert obj0_wr() is None
+        break  # Comment out for manual leak checking (use `top` command).
+
+
+# As of 2021-07-10 the pybind11 GitHub Actions valgrind build uses Python 3.9.
+WORKAROUND_ENABLING_ROLLBACK_OF_PR3068 = env.LINUX and sys.version_info == (3, 9)
+
+
+def test_std_make_shared_factory():
+    class PySftMakeShared(m.Sft):
+        def __init__(self, history):
+            super().__init__(history, 0)
+
+    obj = PySftMakeShared("PySftMakeShared")
+    assert obj.history == "PySftMakeShared"
+    if WORKAROUND_ENABLING_ROLLBACK_OF_PR3068:
+        try:
+            m.pass_through_shd_ptr(obj)
+        except RuntimeError as e:
+            str_exc_info_value = str(e)
+        else:
+            str_exc_info_value = "RuntimeError NOT RAISED"
+    else:
+        with pytest.raises(RuntimeError) as exc_info:
+            m.pass_through_shd_ptr(obj)
+        str_exc_info_value = str(exc_info.value)
+    assert (
+        str_exc_info_value
+        == "smart_holder_type_casters load_as_shared_ptr failure: not implemented:"
+        " trampoline-self-life-support for external shared_ptr to type inheriting"
+        " from std::enable_shared_from_this."
+    )
diff --git a/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp b/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp
new file mode 100644
index 0000000..ced2ea9
--- /dev/null
+++ b/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp
@@ -0,0 +1,105 @@
+// Copyright (c) 2021 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "pybind11/smart_holder.h"
+#include "pybind11_tests.h"
+
+#include <utility>
+
+namespace pybind11_tests {
+namespace class_sh_trampoline_shared_ptr_cpp_arg {
+
+// For testing whether a python subclass of a C++ object dies when the
+// last python reference is lost
+struct SpBase {
+    // returns true if the base virtual function is called
+    virtual bool is_base_used() { return true; }
+
+    // returns true if there's an associated python instance
+    bool has_python_instance() {
+        auto *tinfo = py::detail::get_type_info(typeid(SpBase));
+        return (bool) py::detail::get_object_handle(this, tinfo);
+    }
+
+    SpBase() = default;
+    SpBase(const SpBase &) = delete;
+    virtual ~SpBase() = default;
+};
+
+std::shared_ptr<SpBase> pass_through_shd_ptr(const std::shared_ptr<SpBase> &obj) { return obj; }
+
+struct PySpBase : SpBase {
+    using SpBase::SpBase;
+    bool is_base_used() override { PYBIND11_OVERRIDE(bool, SpBase, is_base_used); }
+};
+
+struct SpBaseTester {
+    std::shared_ptr<SpBase> get_object() const { return m_obj; }
+    void set_object(std::shared_ptr<SpBase> obj) { m_obj = std::move(obj); }
+    bool is_base_used() { return m_obj->is_base_used(); }
+    bool has_instance() { return (bool) m_obj; }
+    bool has_python_instance() { return m_obj && m_obj->has_python_instance(); }
+    void set_nonpython_instance() { m_obj = std::make_shared<SpBase>(); }
+    std::shared_ptr<SpBase> m_obj;
+};
+
+// For testing that a C++ class without an alias does not retain the python
+// portion of the object
+struct SpGoAway {};
+
+struct SpGoAwayTester {
+    std::shared_ptr<SpGoAway> m_obj;
+};
+
+} // namespace class_sh_trampoline_shared_ptr_cpp_arg
+} // namespace pybind11_tests
+
+using namespace pybind11_tests::class_sh_trampoline_shared_ptr_cpp_arg;
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpBase)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpBaseTester)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpGoAway)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpGoAwayTester)
+
+TEST_SUBMODULE(class_sh_trampoline_shared_ptr_cpp_arg, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    // For testing whether a python subclass of a C++ object dies when the
+    // last python reference is lost
+
+    py::classh<SpBase, PySpBase>(m, "SpBase")
+        .def(py::init<>())
+        .def(py::init([](int) { return std::make_shared<PySpBase>(); }))
+        .def("is_base_used", &SpBase::is_base_used)
+        .def("has_python_instance", &SpBase::has_python_instance);
+
+    m.def("pass_through_shd_ptr", pass_through_shd_ptr);
+    m.def("pass_through_shd_ptr_release_gil",
+          pass_through_shd_ptr,
+          py::call_guard<py::gil_scoped_release>()); // PR #4196
+
+    py::classh<SpBaseTester>(m, "SpBaseTester")
+        .def(py::init<>())
+        .def("get_object", &SpBaseTester::get_object)
+        .def("set_object", &SpBaseTester::set_object)
+        .def("is_base_used", &SpBaseTester::is_base_used)
+        .def("has_instance", &SpBaseTester::has_instance)
+        .def("has_python_instance", &SpBaseTester::has_python_instance)
+        .def("set_nonpython_instance", &SpBaseTester::set_nonpython_instance)
+        .def_readwrite("obj", &SpBaseTester::m_obj);
+
+    // For testing that a C++ class without an alias does not retain the python
+    // portion of the object
+
+    py::classh<SpGoAway>(m, "SpGoAway").def(py::init<>());
+
+    py::classh<SpGoAwayTester>(m, "SpGoAwayTester")
+        .def(py::init<>())
+        .def_readwrite("obj", &SpGoAwayTester::m_obj);
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py b/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py
new file mode 100644
index 0000000..7a44a60
--- /dev/null
+++ b/tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py
@@ -0,0 +1,157 @@
+from __future__ import annotations
+
+import pytest
+
+import env  # noqa: F401
+import pybind11_tests.class_sh_trampoline_shared_ptr_cpp_arg as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
+def test_shared_ptr_cpp_arg():
+    import weakref
+
+    class PyChild(m.SpBase):
+        def is_base_used(self):
+            return False
+
+    tester = m.SpBaseTester()
+
+    obj = PyChild()
+    objref = weakref.ref(obj)
+
+    # Pass the last python reference to the C++ function
+    tester.set_object(obj)
+    del obj
+    pytest.gc_collect()
+
+    # python reference is still around since C++ has it now
+    assert objref() is not None
+    assert tester.is_base_used() is False
+    assert tester.obj.is_base_used() is False
+    assert tester.get_object() is objref()
+
+
+@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
+def test_shared_ptr_cpp_prop():
+    class PyChild(m.SpBase):
+        def is_base_used(self):
+            return False
+
+    tester = m.SpBaseTester()
+
+    # Set the last python reference as a property of the C++ object
+    tester.obj = PyChild()
+    pytest.gc_collect()
+
+    # python reference is still around since C++ has it now
+    assert tester.is_base_used() is False
+    assert tester.has_python_instance() is True
+    assert tester.obj.is_base_used() is False
+    assert tester.obj.has_python_instance() is True
+
+
+@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
+def test_shared_ptr_arg_identity():
+    import weakref
+
+    tester = m.SpBaseTester()
+
+    obj = m.SpBase()
+    objref = weakref.ref(obj)
+
+    tester.set_object(obj)
+    del obj
+    pytest.gc_collect()
+
+    # NOTE: the behavior below is DIFFERENT from PR #2839
+    # python reference is gone because it is not an Alias instance
+    assert objref() is None
+    assert tester.has_python_instance() is False
+
+
+@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
+def test_shared_ptr_alias_nonpython():
+    tester = m.SpBaseTester()
+
+    # C++ creates the object, a python instance shouldn't exist
+    tester.set_nonpython_instance()
+    assert tester.is_base_used() is True
+    assert tester.has_instance() is True
+    assert tester.has_python_instance() is False
+
+    # Now a python instance exists
+    cobj = tester.get_object()
+    assert cobj.has_python_instance()
+    assert tester.has_instance() is True
+    assert tester.has_python_instance() is True
+
+    # Now it's gone
+    del cobj
+    pytest.gc_collect()
+    assert tester.has_instance() is True
+    assert tester.has_python_instance() is False
+
+    # When we pass it as an arg to a new tester the python instance should
+    # disappear because it wasn't created with an alias
+    new_tester = m.SpBaseTester()
+
+    cobj = tester.get_object()
+    assert cobj.has_python_instance()
+
+    new_tester.set_object(cobj)
+    assert tester.has_python_instance() is True
+    assert new_tester.has_python_instance() is True
+
+    del cobj
+    pytest.gc_collect()
+
+    # Gone!
+    assert tester.has_instance() is True
+    assert tester.has_python_instance() is False
+    assert new_tester.has_instance() is True
+    assert new_tester.has_python_instance() is False
+
+
+@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
+def test_shared_ptr_goaway():
+    import weakref
+
+    tester = m.SpGoAwayTester()
+
+    obj = m.SpGoAway()
+    objref = weakref.ref(obj)
+
+    assert tester.obj is None
+
+    tester.obj = obj
+    del obj
+    pytest.gc_collect()
+
+    # python reference is no longer around
+    assert objref() is None
+    # C++ reference is still around
+    assert tester.obj is not None
+
+
+def test_infinite():
+    tester = m.SpBaseTester()
+    while True:
+        tester.set_object(m.SpBase())
+        break  # Comment out for manual leak checking (use `top` command).
+
+
+@pytest.mark.parametrize(
+    "pass_through_func", [m.pass_through_shd_ptr, m.pass_through_shd_ptr_release_gil]
+)
+def test_std_make_shared_factory(pass_through_func):
+    class PyChild(m.SpBase):
+        def __init__(self):
+            super().__init__(0)
+
+    obj = PyChild()
+    while True:
+        assert pass_through_func(obj) is obj
+        break  # Comment out for manual leak checking (use `top` command).
diff --git a/tests/test_class_sh_trampoline_unique_ptr.cpp b/tests/test_class_sh_trampoline_unique_ptr.cpp
new file mode 100644
index 0000000..72c0ca5
--- /dev/null
+++ b/tests/test_class_sh_trampoline_unique_ptr.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) 2021 The Pybind Development Team.
+// All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include "pybind11/smart_holder.h"
+#include "pybind11/trampoline_self_life_support.h"
+#include "pybind11_tests.h"
+
+#include <cstdint>
+
+namespace pybind11_tests {
+namespace class_sh_trampoline_unique_ptr {
+
+class Class {
+public:
+    virtual ~Class() = default;
+
+    void setVal(std::uint64_t val) { val_ = val; }
+    std::uint64_t getVal() const { return val_; }
+
+    virtual std::unique_ptr<Class> clone() const = 0;
+    virtual int foo() const = 0;
+
+protected:
+    Class() = default;
+
+    // Some compilers complain about implicitly defined versions of some of the following:
+    Class(const Class &) = default;
+
+private:
+    std::uint64_t val_ = 0;
+};
+
+} // namespace class_sh_trampoline_unique_ptr
+} // namespace pybind11_tests
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_trampoline_unique_ptr::Class)
+
+namespace pybind11_tests {
+namespace class_sh_trampoline_unique_ptr {
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+class PyClass : public Class, public py::trampoline_self_life_support {
+public:
+    std::unique_ptr<Class> clone() const override {
+        PYBIND11_OVERRIDE_PURE(std::unique_ptr<Class>, Class, clone);
+    }
+
+    int foo() const override { PYBIND11_OVERRIDE_PURE(int, Class, foo); }
+};
+#endif
+
+} // namespace class_sh_trampoline_unique_ptr
+} // namespace pybind11_tests
+
+TEST_SUBMODULE(class_sh_trampoline_unique_ptr, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    using namespace pybind11_tests::class_sh_trampoline_unique_ptr;
+
+    py::classh<Class, PyClass>(m, "Class")
+        .def(py::init<>())
+        .def("set_val", &Class::setVal)
+        .def("get_val", &Class::getVal)
+        .def("clone", &Class::clone)
+        .def("foo", &Class::foo);
+
+    m.def("clone", [](const Class &obj) { return obj.clone(); });
+    m.def("clone_and_foo", [](const Class &obj) { return obj.clone()->foo(); });
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_trampoline_unique_ptr.py b/tests/test_class_sh_trampoline_unique_ptr.py
new file mode 100644
index 0000000..4044761
--- /dev/null
+++ b/tests/test_class_sh_trampoline_unique_ptr.py
@@ -0,0 +1,36 @@
+from __future__ import annotations
+
+import pytest
+
+import pybind11_tests.class_sh_trampoline_unique_ptr as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+class MyClass(m.Class):
+    def foo(self):
+        return 10 + self.get_val()
+
+    def clone(self):
+        cloned = MyClass()
+        cloned.set_val(self.get_val() + 3)
+        return cloned
+
+
+def test_m_clone():
+    obj = MyClass()
+    while True:
+        obj.set_val(5)
+        obj = m.clone(obj)
+        assert obj.get_val() == 5 + 3
+        assert obj.foo() == 10 + 5 + 3
+        return  # Comment out for manual leak checking (use `top` command).
+
+
+def test_m_clone_and_foo():
+    obj = MyClass()
+    obj.set_val(7)
+    while True:
+        assert m.clone_and_foo(obj) == 10 + 7 + 3
+        return  # Comment out for manual leak checking (use `top` command).
diff --git a/tests/test_class_sh_unique_ptr_custom_deleter.cpp b/tests/test_class_sh_unique_ptr_custom_deleter.cpp
new file mode 100644
index 0000000..4bc4566
--- /dev/null
+++ b/tests/test_class_sh_unique_ptr_custom_deleter.cpp
@@ -0,0 +1,47 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+
+namespace pybind11_tests {
+namespace class_sh_unique_ptr_custom_deleter {
+
+// Reduced from a PyCLIF use case in the wild by @wangxf123456.
+class Pet {
+public:
+    using Ptr = std::unique_ptr<Pet, std::function<void(Pet *)>>;
+
+    std::string name;
+
+    static Ptr New(const std::string &name) {
+        return Ptr(new Pet(name), std::default_delete<Pet>());
+    }
+
+private:
+    explicit Pet(const std::string &name) : name(name) {}
+};
+
+} // namespace class_sh_unique_ptr_custom_deleter
+} // namespace pybind11_tests
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_unique_ptr_custom_deleter::Pet)
+
+namespace pybind11_tests {
+namespace class_sh_unique_ptr_custom_deleter {
+
+TEST_SUBMODULE(class_sh_unique_ptr_custom_deleter, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    py::classh<Pet>(m, "Pet").def_readwrite("name", &Pet::name);
+
+    m.def("create", &Pet::New);
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
+
+} // namespace class_sh_unique_ptr_custom_deleter
+} // namespace pybind11_tests
diff --git a/tests/test_class_sh_unique_ptr_custom_deleter.py b/tests/test_class_sh_unique_ptr_custom_deleter.py
new file mode 100644
index 0000000..8658de8
--- /dev/null
+++ b/tests/test_class_sh_unique_ptr_custom_deleter.py
@@ -0,0 +1,13 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_unique_ptr_custom_deleter as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+def test_create():
+    pet = m.create("abc")
+    assert pet.name == "abc"
diff --git a/tests/test_class_sh_unique_ptr_member.cpp b/tests/test_class_sh_unique_ptr_member.cpp
new file mode 100644
index 0000000..90df0c6
--- /dev/null
+++ b/tests/test_class_sh_unique_ptr_member.cpp
@@ -0,0 +1,67 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+
+namespace pybind11_tests {
+namespace class_sh_unique_ptr_member {
+
+class pointee { // NOT copyable.
+public:
+    pointee() = default;
+
+    int get_int() const { return 213; }
+
+    pointee(const pointee &) = delete;
+    pointee(pointee &&) = delete;
+    pointee &operator=(const pointee &) = delete;
+    pointee &operator=(pointee &&) = delete;
+};
+
+inline std::unique_ptr<pointee> make_unique_pointee() {
+    return std::unique_ptr<pointee>(new pointee);
+}
+
+class ptr_owner {
+public:
+    explicit ptr_owner(std::unique_ptr<pointee> ptr) : ptr_(std::move(ptr)) {}
+
+    bool is_owner() const { return bool(ptr_); }
+
+    std::unique_ptr<pointee> give_up_ownership_via_unique_ptr() { return std::move(ptr_); }
+    std::shared_ptr<pointee> give_up_ownership_via_shared_ptr() { return std::move(ptr_); }
+
+private:
+    std::unique_ptr<pointee> ptr_;
+};
+
+} // namespace class_sh_unique_ptr_member
+} // namespace pybind11_tests
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_unique_ptr_member::pointee)
+
+namespace pybind11_tests {
+namespace class_sh_unique_ptr_member {
+
+TEST_SUBMODULE(class_sh_unique_ptr_member, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    py::classh<pointee>(m, "pointee").def(py::init<>()).def("get_int", &pointee::get_int);
+
+    m.def("make_unique_pointee", make_unique_pointee);
+
+    py::class_<ptr_owner>(m, "ptr_owner")
+        .def(py::init<std::unique_ptr<pointee>>(), py::arg("ptr"))
+        .def("is_owner", &ptr_owner::is_owner)
+        .def("give_up_ownership_via_unique_ptr", &ptr_owner::give_up_ownership_via_unique_ptr)
+        .def("give_up_ownership_via_shared_ptr", &ptr_owner::give_up_ownership_via_shared_ptr);
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
+
+} // namespace class_sh_unique_ptr_member
+} // namespace pybind11_tests
diff --git a/tests/test_class_sh_unique_ptr_member.py b/tests/test_class_sh_unique_ptr_member.py
new file mode 100644
index 0000000..25bcba9
--- /dev/null
+++ b/tests/test_class_sh_unique_ptr_member.py
@@ -0,0 +1,29 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_unique_ptr_member as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+def test_make_unique_pointee():
+    obj = m.make_unique_pointee()
+    assert obj.get_int() == 213
+
+
+@pytest.mark.parametrize(
+    "give_up_ownership_via",
+    ["give_up_ownership_via_unique_ptr", "give_up_ownership_via_shared_ptr"],
+)
+def test_pointee_and_ptr_owner(give_up_ownership_via):
+    obj = m.pointee()
+    assert obj.get_int() == 213
+    owner = m.ptr_owner(obj)
+    with pytest.raises(ValueError, match="Python instance was disowned"):
+        obj.get_int()
+    assert owner.is_owner()
+    reclaimed = getattr(owner, give_up_ownership_via)()
+    assert not owner.is_owner()
+    assert reclaimed.get_int() == 213
diff --git a/tests/test_class_sh_virtual_py_cpp_mix.cpp b/tests/test_class_sh_virtual_py_cpp_mix.cpp
new file mode 100644
index 0000000..28f7d8f
--- /dev/null
+++ b/tests/test_class_sh_virtual_py_cpp_mix.cpp
@@ -0,0 +1,75 @@
+#include <pybind11/smart_holder.h>
+
+#include "pybind11_tests.h"
+
+#include <memory>
+
+namespace pybind11_tests {
+namespace class_sh_virtual_py_cpp_mix {
+
+class Base {
+public:
+    virtual ~Base() = default;
+    virtual int get() const { return 101; }
+
+    // Some compilers complain about implicitly defined versions of some of the following:
+    Base() = default;
+    Base(const Base &) = default;
+};
+
+class CppDerivedPlain : public Base {
+public:
+    int get() const override { return 202; }
+};
+
+class CppDerived : public Base {
+public:
+    int get() const override { return 212; }
+};
+
+int get_from_cpp_plainc_ptr(const Base *b) { return b->get() + 4000; }
+
+int get_from_cpp_unique_ptr(std::unique_ptr<Base> b) { return b->get() + 5000; }
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+struct BaseVirtualOverrider : Base, py::trampoline_self_life_support {
+    using Base::Base;
+
+    int get() const override { PYBIND11_OVERRIDE(int, Base, get); }
+};
+
+struct CppDerivedVirtualOverrider : CppDerived, py::trampoline_self_life_support {
+    using CppDerived::CppDerived;
+
+    int get() const override { PYBIND11_OVERRIDE(int, CppDerived, get); }
+};
+
+#endif
+
+} // namespace class_sh_virtual_py_cpp_mix
+} // namespace pybind11_tests
+
+using namespace pybind11_tests::class_sh_virtual_py_cpp_mix;
+
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(Base)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(CppDerivedPlain)
+PYBIND11_SMART_HOLDER_TYPE_CASTERS(CppDerived)
+
+TEST_SUBMODULE(class_sh_virtual_py_cpp_mix, m) {
+    m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
+#ifndef PYBIND11_SMART_HOLDER_ENABLED
+        false;
+#else
+        true;
+
+    py::classh<Base, BaseVirtualOverrider>(m, "Base").def(py::init<>()).def("get", &Base::get);
+
+    py::classh<CppDerivedPlain, Base>(m, "CppDerivedPlain").def(py::init<>());
+
+    py::classh<CppDerived, Base, CppDerivedVirtualOverrider>(m, "CppDerived").def(py::init<>());
+
+    m.def("get_from_cpp_plainc_ptr", get_from_cpp_plainc_ptr, py::arg("b"));
+    m.def("get_from_cpp_unique_ptr", get_from_cpp_unique_ptr, py::arg("b"));
+#endif // PYBIND11_SMART_HOLDER_ENABLED
+}
diff --git a/tests/test_class_sh_virtual_py_cpp_mix.py b/tests/test_class_sh_virtual_py_cpp_mix.py
new file mode 100644
index 0000000..937a15a
--- /dev/null
+++ b/tests/test_class_sh_virtual_py_cpp_mix.py
@@ -0,0 +1,69 @@
+from __future__ import annotations
+
+import pytest
+
+from pybind11_tests import class_sh_virtual_py_cpp_mix as m
+
+if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
+    pytest.skip("smart_holder not available.", allow_module_level=True)
+
+
+class PyBase(m.Base):  # Avoiding name PyDerived, for more systematic naming.
+    def __init__(self):
+        m.Base.__init__(self)
+
+    def get(self):
+        return 323
+
+
+class PyCppDerived(m.CppDerived):
+    def __init__(self):
+        m.CppDerived.__init__(self)
+
+    def get(self):
+        return 434
+
+
+@pytest.mark.parametrize(
+    ("ctor", "expected"),
+    [
+        (m.Base, 101),
+        (PyBase, 323),
+        (m.CppDerivedPlain, 202),
+        (m.CppDerived, 212),
+        (PyCppDerived, 434),
+    ],
+)
+def test_base_get(ctor, expected):
+    obj = ctor()
+    assert obj.get() == expected
+
+
+@pytest.mark.parametrize(
+    ("ctor", "expected"),
+    [
+        (m.Base, 4101),
+        (PyBase, 4323),
+        (m.CppDerivedPlain, 4202),
+        (m.CppDerived, 4212),
+        (PyCppDerived, 4434),
+    ],
+)
+def test_get_from_cpp_plainc_ptr(ctor, expected):
+    obj = ctor()
+    assert m.get_from_cpp_plainc_ptr(obj) == expected
+
+
+@pytest.mark.parametrize(
+    ("ctor", "expected"),
+    [
+        (m.Base, 5101),
+        (PyBase, 5323),
+        (m.CppDerivedPlain, 5202),
+        (m.CppDerived, 5212),
+        (PyCppDerived, 5434),
+    ],
+)
+def test_get_from_cpp_unique_ptr(ctor, expected):
+    obj = ctor()
+    assert m.get_from_cpp_unique_ptr(obj) == expected
diff --git a/ubench/holder_comparison.cpp b/ubench/holder_comparison.cpp
new file mode 100644
index 0000000..feed3a0
--- /dev/null
+++ b/ubench/holder_comparison.cpp
@@ -0,0 +1,57 @@
+#include <pybind11/pybind11.h>
+
+#include "number_bucket.h"
+
+#include <cstddef>
+#include <memory>
+
+namespace hc { // holder comparison
+
+using nb_up = pybind11_ubench::number_bucket<1>;
+using nb_sp = pybind11_ubench::number_bucket<2>;
+using nb_pu = pybind11_ubench::number_bucket<3>;
+using nb_sh = pybind11_ubench::number_bucket<4>;
+
+namespace py = pybind11;
+
+template <typename WrappedType, typename HolderType>
+void wrap_number_bucket(py::module m, const char *class_name) {
+    py::class_<WrappedType, HolderType>(m, class_name)
+        .def(py::init<std::size_t>(), py::arg("data_size") = 0)
+        .def("sum", &WrappedType::sum)
+        .def("add", &WrappedType::add, py::arg("other"));
+}
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+
+template <typename T>
+class padded_unique_ptr {
+    std::unique_ptr<T> ptr;
+    char padding[sizeof(py::smart_holder) - sizeof(std::unique_ptr<T>)];
+
+public:
+    padded_unique_ptr(T *p) : ptr(p) {}
+    T *get() { return ptr.get(); }
+};
+
+static_assert(sizeof(padded_unique_ptr<nb_pu>) == sizeof(py::smart_holder),
+              "Unexpected sizeof mismatch.");
+
+#endif
+
+} // namespace hc
+
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+PYBIND11_DECLARE_HOLDER_TYPE(T, hc::padded_unique_ptr<T>);
+#endif
+
+PYBIND11_MODULE(pybind11_ubench_holder_comparison, m) {
+    using namespace hc;
+    wrap_number_bucket<nb_up, std::unique_ptr<nb_up>>(m, "number_bucket_up");
+    wrap_number_bucket<nb_sp, std::shared_ptr<nb_sp>>(m, "number_bucket_sp");
+#ifdef PYBIND11_SMART_HOLDER_ENABLED
+    m.def("sizeof_smart_holder", []() { return sizeof(py::smart_holder); });
+    wrap_number_bucket<nb_pu, padded_unique_ptr<nb_pu>>(m, "number_bucket_pu");
+    wrap_number_bucket<nb_sh, py::smart_holder>(m, "number_bucket_sh");
+#endif
+}
diff --git a/ubench/holder_comparison.py b/ubench/holder_comparison.py
new file mode 100644
index 0000000..d18a123
--- /dev/null
+++ b/ubench/holder_comparison.py
@@ -0,0 +1,144 @@
+"""Simple comparison of holder performances, relative to unique_ptr holder."""
+
+# ruff: noqa
+# This code has no unit tests.
+# ruff cleanup deferred until the next time this code is actually used.
+
+import collections
+import sys
+import time
+from typing import Any, Callable, Dict, List
+
+import pybind11_ubench_holder_comparison as m  # type: ignore[import-not-found]
+
+number_bucket_pc = None
+
+
+def pflush(*args: Any, **kwargs: Any) -> None:
+    print(*args, **kwargs)
+    # Using "file" here because it is the name of the built-in keyword argument.
+    file = kwargs.get("file", sys.stdout)  # pylint: disable=redefined-builtin
+    file.flush()  # file object must have a flush method.
+
+
+def run(args: List[str]) -> None:
+    if not args:
+        size_exponent_min = 0
+        size_exponent_max = 16
+        size_exponent_step = 4
+        call_repetitions_first_pass = 100
+        call_repetitions_target_elapsed_secs = 0.1
+        num_samples = 10
+        selected_holder_type = "all"
+    else:
+        assert len(args) == 7, (
+            "size_exponent_min size_exponent_max size_exponent_step"
+            " call_repetitions_first_pass call_repetitions_target_elapsed_secs"
+            " num_samples selected_holder_type"
+        )
+        size_exponent_min = int(args[0])
+        size_exponent_max = int(args[1])
+        size_exponent_step = int(args[2])
+        call_repetitions_first_pass = int(args[3])
+        call_repetitions_target_elapsed_secs = float(args[4])
+        num_samples = int(args[5])
+        selected_holder_type = args[6]
+    pflush(
+        "command-line arguments:",
+        size_exponent_min,
+        size_exponent_max,
+        size_exponent_step,
+        call_repetitions_first_pass,
+        "%.3f" % call_repetitions_target_elapsed_secs,
+        num_samples,
+        selected_holder_type,
+    )
+    pflush("sizeof_smart_holder:", m.sizeof_smart_holder())
+
+    def find_call_repetitions(
+        callable: Callable[[int], float],
+        time_delta_floor: float = 1.0e-6,
+        target_elapsed_secs_multiplier: float = 1.05,  # Empirical.
+        target_elapsed_secs_tolerance: float = 0.05,
+        max_iterations: int = 100,
+    ) -> int:
+        td_target = (
+            call_repetitions_target_elapsed_secs * target_elapsed_secs_multiplier
+        )
+        crd = call_repetitions_first_pass
+        for _ in range(max_iterations):
+            td = callable(crd)
+            crd = max(1, int(td_target * crd / max(td, time_delta_floor)))
+            if abs(td - td_target) / td_target < target_elapsed_secs_tolerance:
+                return crd
+        raise RuntimeError("find_call_repetitions failure: max_iterations exceeded.")
+
+    for size_exponent in range(
+        size_exponent_min, size_exponent_max + 1, size_exponent_step
+    ):
+        data_size = 2**size_exponent
+        pflush(data_size, "data_size")
+        ratios: Dict[str, List[float]] = collections.defaultdict(list)
+        call_repetitions = None
+        for _ in range(num_samples):
+            row_0 = None
+            for nb_label, nb_type in [
+                ("up", m.number_bucket_up),
+                ("sp", m.number_bucket_sp),
+                ("pu", m.number_bucket_pu),
+                ("sh", m.number_bucket_sh),
+                ("pc", number_bucket_pc),
+            ]:
+                if nb_label == "pc" and nb_type is None:
+                    continue
+                if selected_holder_type != "all" and nb_label != selected_holder_type:
+                    continue
+                nb1 = nb_type(data_size)
+                nb2 = nb_type(data_size)
+
+                def many_sum(call_repetitions: int) -> float:
+                    assert int(round(nb1.sum())) == data_size
+                    t0 = time.time()
+                    for _ in range(call_repetitions):
+                        nb1.sum()
+                    return time.time() - t0
+
+                def many_add(call_repetitions: int) -> float:
+                    assert nb1.add(nb2) == data_size
+                    t0 = time.time()
+                    for _ in range(call_repetitions):
+                        nb1.add(nb2)
+                    return time.time() - t0
+
+                if call_repetitions is None:
+                    call_repetitions = find_call_repetitions(many_sum)
+                    pflush(call_repetitions, "call_repetitions")
+
+                td_sum = many_sum(call_repetitions)
+                td_add = many_add(call_repetitions)
+                row = [td_sum, td_add]
+                if row_0 is None:
+                    pflush("     Sum   Add  ratS  ratA")
+                    row_0 = row
+                else:
+                    for curr, prev in zip(row, row_0):  # type: ignore[unreachable]
+                        if prev:
+                            rat = curr / prev
+                        else:
+                            rat = -1
+                        row.append(curr / prev)
+                    ratios[nb_label + "_ratS"].append(row[-2])
+                    ratios[nb_label + "_ratA"].append(row[-1])
+                pflush(nb_label, " ".join(["%.3f" % v for v in row]))
+        pflush("          Min  Mean   Max")
+        for key, rat in ratios.items():
+            print(
+                key,
+                "{:5.3f} {:5.3f} {:5.3f}".format(
+                    min(rat), sum(rat) / len(rat), max(rat)
+                ),
+            )
+
+
+if __name__ == "__main__":
+    run(args=sys.argv[1:])
diff --git a/ubench/holder_comparison_extract_sheet_data.py b/ubench/holder_comparison_extract_sheet_data.py
new file mode 100644
index 0000000..0676603
--- /dev/null
+++ b/ubench/holder_comparison_extract_sheet_data.py
@@ -0,0 +1,71 @@
+"""Extract mean ratios from holder_comparison.py output."""
+
+# ruff: noqa
+# This code has no unit tests.
+# ruff cleanup deferred until the next time this code is actually used.
+
+import sys
+from typing import List, Optional
+
+
+def run(args: List[str]) -> None:
+    assert len(args) == 1, "log_holder_comparison.txt"
+
+    log_lines = open(args[0]).read().splitlines()
+
+    for ratx in ("_ratS ", "_ratA "):
+        print(ratx)
+        header = None
+        header_row = None
+        data_row = None
+        data_row_buffer: List[List[str]] = []
+
+        def show() -> Optional[List[str]]:
+            if header_row:
+                if header is None:  # type: ignore[unreachable]
+                    print(",".join(header_row))
+                else:
+                    assert header == header_row
+            if data_row is not None:
+                print(",".join(data_row))  # type: ignore[unreachable]
+                data_row_buffer.append(data_row)
+            return header_row
+
+        for line in log_lines:
+            if line.endswith(" data_size"):
+                header = show()
+                flds = line.split()
+                assert len(flds) == 2
+                header_row = ["data_size"]
+                data_row = [flds[0]]
+            elif line.endswith(" call_repetitions"):
+                flds = line.split()
+                assert len(flds) == 2
+                assert header_row is not None
+                assert data_row is not None
+                header_row.append("calls")
+                data_row.append(flds[0])
+                header_row.append("up")
+                data_row.append("1.000")
+            elif line[2:].startswith(ratx):
+                flds = line.split()
+                assert len(flds) == 4
+                assert header_row is not None
+                assert data_row is not None
+                header_row.append(line[:2])
+                data_row.append(flds[2])
+        show()
+
+        assert header_row is not None
+        print("Scaled to last column:")
+        print(",".join(header_row))
+        for data_row in data_row_buffer:
+            data_row_rescaled = data_row[:2]
+            unit = float(data_row[-1])
+            for fld in data_row[2:]:
+                data_row_rescaled.append("%.3f" % (float(fld) / unit))
+            print(",".join(data_row_rescaled))
+
+
+if __name__ == "__main__":
+    run(args=sys.argv[1:])
diff --git a/ubench/number_bucket.h b/ubench/number_bucket.h
new file mode 100644
index 0000000..fe04a1e
--- /dev/null
+++ b/ubench/number_bucket.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include <cstddef>
+#include <exception>
+#include <iostream>
+#include <vector>
+
+namespace pybind11_ubench {
+
+template <int Serial>
+struct number_bucket {
+    std::vector<double> data;
+
+    explicit number_bucket(std::size_t data_size = 0) : data(data_size, 1.0) {}
+
+    double sum() const {
+        std::size_t n = 0;
+        double s = 0;
+        const double *a = &*data.begin();
+        const double *e = &*data.end();
+        while (a != e) {
+            s += *a++;
+            n++;
+        }
+        if (n != data.size()) {
+            std::cerr << "Internal consistency failure (sum)." << std::endl;
+            std::terminate();
+        }
+        return s;
+    }
+
+    std::size_t add(const number_bucket &other) {
+        if (other.data.size() != data.size()) {
+            std::cerr << "Incompatible data sizes (add)." << std::endl;
+            std::terminate();
+        }
+        std::size_t n = 0;
+        double *a = &*data.begin();
+        const double *e = &*data.end();
+        const double *b = &*other.data.begin();
+        while (a != e) {
+            *a++ += *b++;
+            n++;
+        }
+        return n;
+    }
+
+private:
+    number_bucket(const number_bucket &) = delete;
+    number_bucket(number_bucket &&) = delete;
+    number_bucket &operator=(const number_bucket &) = delete;
+    number_bucket &operator=(number_bucket &&) = delete;
+};
+
+} // namespace pybind11_ubench
diff --git a/ubench/python/number_bucket.clif b/ubench/python/number_bucket.clif
new file mode 100644
index 0000000..ef704c0
--- /dev/null
+++ b/ubench/python/number_bucket.clif
@@ -0,0 +1,6 @@
+from "pybind11/ubench/number_bucket.h":
+  namespace `pybind11_ubench`:
+    class `number_bucket<0>` as number_bucket_pc:
+      def __init__(self, data_size: int = default)
+      def sum(self) -> float
+      def add(self, other: number_bucket_pc) -> int