Как избежать выделения большой памяти с помощью std:: make_shared

Скажем, что у меня есть некоторый произвольный класс, A:

class A {
 //... stuff
};

Я хочу вызвать внешний API, который использует общий указатель на некоторый тип, например (Я не могу изменить этот интерфейс):

//...much later
void foo(std::shared_ptr<A> _a){
    //operate on _a as a shared_ptr
}

Однако в (устаревшем) коде, с которым я работаю, экземпляр класса A, с которым я работаю, выделяется в стеке (, который я не могу обойти):

A a;
//...some stuff on a
//Now time to call foo

Кроме того, экземпляр класса A довольно велик, порядка 1 ГБ на каждый экземпляр.

Я знаю, что могу позвонить

foo(std::make_shared<A> a);

но это выделило бы память для копии A, чего я бы очень хотел избежать.

Вопрос

Есть ли способ взломать некоторый вызов std::make_shared (возможно с семантикой move), так что я не вынужден выделять память для другого экземпляра класса A?

Я пробовал что-то вроде этого:

foo(std::make_shared<A>(std::move(a)));

Но из того, что я могу сказать, новый экземпляр A все еще создан.

Пример кода

#include <iostream>
#include <memory>
using namespace std;


class A{
    public:
    A(int _var=42) : var(_var){cout << "Default" << endl;}
    A(const A& _rhs) : var(_rhs.var){cout << "Copy" << endl;}
    A(A&& _rhs) : var(std::move(_rhs.var)){cout << "Move" << endl;}
    int var;
};

void foo(std::shared_ptr<A> _a){
    _a->var = 43;
    cout << _a->var << endl;
}

int main() {
    A a;
    cout << a.var << endl;
    foo(std::make_shared<A>(std::move(a)));
    cout << a.var << endl;
    a.var = 44;
    foo(std::make_shared<A>(std::move(a)));
    cout << a.var << endl;
    return 0;
}

Вывод:

По умолчанию
42
Перемещение
43
42
Перемещение
43
44

Ответ 1

Это возможно с помощью конструктора shared_ptr, который допускает "пустой экземпляр с ненулевым сохраненным указателем":

A x;
std::shared_ptr<A> i_dont_own(std::shared_ptr<A>(), &x);

( "Перегрузка (8)" в документации cppreference.)

Ответ 2

Если вы знаете, что общий указатель, который вы передаете на foo(), не будет сохранен, скопирован и т.д., то есть не будет переживать ваш объект, вы можете сделать std::shared_ptr направленным на объект в стеке с пустым удалением:

void emptyDeleter( A * ) {}

A a;
foo( std::shared_ptr<A>( &a, emptyDeleter ) );

Снова вам нужно убедиться, что общий указатель или его копия не оживут объект и хорошо документируют этот взлом.

Ответ 3

Предполагая, что класс A поддерживает семантику переноса, выполните следующее:

std::shared_ptr<A> newA = make_shared<A> (std::move (_a));

Не используйте _a больше, используйте только newA. Теперь вы можете передать newA в функцию.

Если класс A не поддерживает семантику перемещения, нет безопасного/разумного способа сделать это. Любой взлом будет работать только, и может разорваться в будущем. Если вы достаточно контролируете код класса, вы можете добавить поддержку семантики перемещения.

Но из того, что я могу сказать, новый экземпляр A все еще создан.

Почему вас это волнует? То, что вы пытаетесь избежать, - это копирование всех данных в экземпляре, и это делает это.

Семантика точки перемещения - перемещать данные из одного экземпляра в другой без необходимости делать allocate/copy/free. Конечно, это делает исходный экземпляр "пустым", поэтому больше не используйте его.