Можно ли определить как был создан shared_ptr: через make_shared или через конструктор?
Имеем два std::shared_ptr указателя на объекты:
p1, созданный с помощьюstd::make_shared<>().p2, созданный с помощью конструктора классаstd::shared_ptr.
std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
Есть куча информации о том, что поведение p1 и p2 несколько различается (Difference in make_shared and normal shared_ptr, раздел Notes на cppreference, и т. д.), и есть ситуации где один метод имеет преимущества перед другим.
Вопрос: возможно ли имея p1 и p2 определить во время работы программы, были ли они созданы через std::make_shared или через конструктор?
Если нельзя этого сказать точно, можно ли попробовать "покопаться внутри" и сделать обоснованное предположение?
Стандарт C++ не имеет значения (хоть С++23), также подойдут решения, специфичные для конкретных компиляторов, если это делает задачу проще.
Ответы (1 шт):
Проверял на трех стандартных библиотеках: libstdc++, libc++, и MSVC STL.
На всех трех, make_shared хранит мета-информацию и объект в одном блоке памяти (чтобы два раза не звать new), а при ручном выделении памяти этого не происходит.
На всех трех, shared_ptr хранит в себе два указателя - на объект и на мета-информацию, именно в таком порядке.
Берем второй указатель, и проверяем, указывает ли он в середину блока памяти, лежащей по первому указателю, или еще куда-то. Для этого нужно знать длину этого блока - для этого есть всякие платформо-зависимые функции (проверял только на линуксе и на mingw через wine, мака нет).
#include <cstddef>
#include <cstring>
#include <iostream>
#include <memory>
#ifdef __APPLE__
#include <malloc/malloc.h>
#else // Windows and Linux
#include <malloc.h>
#endif
template <typename T>
bool is_from_make_shared(const std::shared_ptr<T> &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)) << '\n'; // 1
std::cout << is_from_make_shared(std::shared_ptr<int>(new int(42))) << '\n'; // 0
std::cout << is_from_make_shared(std::make_shared<A>()) << '\n'; // 1
std::cout << is_from_make_shared(std::shared_ptr<A>(new A)) << '\n'; // 1
}