Можно ли определить как был создан shared_ptr: через make_shared или через конструктор?

1,00
р.
Имеем два std::shared_ptr указателя на объекты:
p1, созданный с помощью std::make_shared<>(). p2, созданный с помощью конструктора класса std::shared_ptr.
std::shared_ptr p1 = std::make_shared("foo") std::shared_ptr p2(new Object("foo"))
Есть куча информации о том, что поведение p1 и p2 несколько различается (Difference in make_shared and normal shared_ptr, раздел Notes на cppreference, и т. д.), и есть ситуации где один метод имеет преимущества перед другим.
Вопрос: возможно ли имея p1 и p2 определить во время работы программы, были ли они созданы через std::make_shared или через конструктор?
Если нельзя этого сказать точно, можно ли попробовать "покопаться внутри" и сделать обоснованное предположение?
Стандарт C++ не имеет значения (хоть С++23), также подойдут решения, специфичные для конкретных компиляторов, если это делает задачу проще.

Ответ
Проверял на трех стандартных библиотеках: libstdc++, libc++, и MSVC STL.
На всех трех, make_shared хранит мета-информацию и объект в одном блоке памяти (чтобы два раза не звать new), а при ручном выделении памяти этого не происходит.
На всех трех, shared_ptr хранит в себе два указателя - на объект и на мета-информацию, именно в таком порядке.
Берем второй указатель, и проверяем, указывает ли он в середину блока памяти, лежащей по первому указателю, или еще куда-то. Для этого нужно знать длину этого блока - для этого есть всякие платформо-зависимые функции (проверял только на линуксе и на mingw через wine, мака нет).
#include #include #include #include
#ifdef __APPLE__ #include #else // Windows and Linux #include #endif
template bool is_from_make_shared(const std::shared_ptr &ptr) { static_assert(sizeof(ptr) == sizeof(void *) * 2) char *a, *b // a is object ptr, b is control block ptr. std::memcpy(&a, &ptr, sizeof(char *)) std::memcpy(&b, (char *)&ptr + sizeof(char *), sizeof(char *))
if (a < b) return false
std::size_t size = #if defined(_WIN32) _msize(b) #elif defined(__APPLE__) malloc_size(b) #else malloc_usable_size(b) #endif
return (char *)a < b + size }
struct alignas(64) A {}
int main() { std::cout << is_from_make_shared(std::make_shared<int>(42)) << '<br>' // 1 std::cout << is_from_make_shared(std::shared_ptr<int>(new int(42))) << '<br>' // 0 std::cout << is_from_make_shared(std::make_shared<A>()) << '<br>' // 1 std::cout << is_from_make_shared(std::shared_ptr<A>(new A)) << '<br>' // 1 }