Case 80: Pimpl alias changed from shared_ptr to unique_ptr¶
| Field | Value |
|---|---|
| Verdict | 🔴 BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS |
| Flags | ABI break, API break |
Detected ChangeKinds |
internal_type_leaks_via_public_api |
| Source files | examples/case80_pimpl_shared_to_unique/ |
Category: Pimpl ABI | Verdict: BREAKING
What breaks¶
oneDAL's pimpl alias is defined as
…and every public class holds its implementation through that alias:
In v2 the alias is rewritten as using pimpl = std::unique_ptr<T>. On most
64-bit platforms sizeof(shared_ptr<T>) == 16 and sizeof(unique_ptr<T>) == 8,
so the containing class even shrinks — the failure isn't "size grew", it is:
- Mangled name of every inline accessor changes. Any header-inlined
getter that touches
impl_is templated on the pimpl type; its mangled symbol differs between v1 and v2. - Destruction model changes. No control block, no atomic refcount;
consumers built against v1 that aliased into the same control block via
shared_ptraliasing constructors now double-free. - Copy semantics flip. v1 was copyable (refcounted share). v2 is
move-only. Consumer code that copied a
descriptorcompiles under v1, refuses under v2.
Real Failure Demo¶
Severity: BREAKING / ABI SHAPE CHANGE
This smoke app still works because it does not copy or share ownership. The real break is that the public object field changes from shared ownership ABI to unique ownership ABI, changing size, copyability, and inline member code expectations.
cmake -S examples -B /tmp/abicheck-examples-build -DCMAKE_BUILD_TYPE=Debug
cmake --build /tmp/abicheck-examples-build --target case80_pimpl_shared_to_unique_app case80_pimpl_shared_to_unique_v2
tmp=$(mktemp -d)
cp /tmp/abicheck-examples-build/case80_pimpl_shared_to_unique/app_v1 "$tmp/"
cp /tmp/abicheck-examples-build/case80_pimpl_shared_to_unique/libv2.so "$tmp/libv1.so"
(cd "$tmp" && LD_LIBRARY_PATH=. ./app_v1)
# class_count = 7 (expect 7)
Why abicheck catches it¶
The existing type_field_type_changed detector fires on descriptor::impl_
(old type std::shared_ptr<detail::descriptor_impl>, new type
std::unique_ptr<detail::descriptor_impl>). The internal_type_leaks_via_public_api
overlay (from PR #238) then escalates it because the new field type
references the same detail:: namespace that the leak detector already
tracks as internal.
Code diff¶
// v1
template <typename T> using pimpl = std::shared_ptr<T>;
// v2 — silent ownership-model change with cascading consequences
template <typename T> using pimpl = std::unique_ptr<T>;
Why a separate case (not subsumed by case41)¶
case41 (type_changes) covers generic field-type changes. case80 is the
pimpl-shaped instance worth carrying as a named regression: the failure
involves three orthogonal axes (mangling, destruction, copyability) and is
specifically the kind of change a maintainer might propose during "modernize
the API" cleanup without realizing it is binary-incompatible.
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hv2.cppv2.h
See also: Examples overview · All BREAKING cases · Category: Breaking.