如何使用std :: make_shared避免大内存分配

And*_*dyG 12 c++ memory-management c++11

假设我有一些任意的类,A:

class A {
 //... stuff
};
Run Code Online (Sandbox Code Playgroud)

我想调用一个外部API,它接受一个类型的共享指针,就像这样(我不能改变这个接口):

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

但是,在我正在使用的(遗留)代码中,我正在使用的类A实例被分配在堆栈上(我无法解决):

A a;
//...some stuff on a
//Now time to call foo
Run Code Online (Sandbox Code Playgroud)

除此之外,A类的实例非常大,每个实例大约1 GB.

我知道我可以打电话

foo(std::make_shared<A> a);
Run Code Online (Sandbox Code Playgroud)

但这会为A副本分配内存,我真的很想避免.

有没有办法将一些调用std::make_shared(可能是move语义)一起破解,这样我就不会被迫为另一个A类实例分配内存?

我尝试过这样的事情:

foo(std::make_shared<A>(std::move(a)));
Run Code Online (Sandbox Code Playgroud)

但据我所知,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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

默认值
42
移动
43
42
移动
43
44

Ker*_* SB 18

shared_ptr对于允许"具有非空存储指针的空实例" 的构造函数,这是可能的:

A x;
std::shared_ptr<A> i_dont_own(std::shared_ptr<A>(), &x);
Run Code Online (Sandbox Code Playgroud)

(这是cppreference文档中的"overload(8)" .)

  • @MooingDuck它的意思是在共享对象本身的所有权的同时,为对象的成员创建例如`shared_ptr`.使用它这种方式绝对有资格作为黑客;) (5认同)
  • @KerrekSB:我以前从未见过这个构造函数,看起来非常奇怪和危险 (3认同)
  • +1这是我可以学会爱的蹩脚代码.与null-deleter解决方案不同,这种结构不分配内存,而且正式是"noexcept".一个怪癖:客户端`foo`,如果它很难检查,就会看到`a.use_count()== 0`,即使`a.get()`返回正确的非空指针. (3认同)
  • @MooingDuck:它*非常奇怪和危险.OP正在做一些令人难以置信的奇怪和危险的事情,也可能感觉非常奇怪. (2认同)

Sla*_*ica 7

如果你知道你传递给的共享指针foo()不会被存储,复制等,即不会比你的对象寿命更长,你可以std::shared_ptr使用空删除器指向堆栈上的对象:

void emptyDeleter( A * ) {}

A a;
foo( std::shared_ptr<A>( &a, emptyDeleter ) );
Run Code Online (Sandbox Code Playgroud)

你需要再次确保共享指针或它的副本不会比对象更长,并且很好地记录了这个hack.

  • 很难想象一个他无法改变界面但可以确保指针不会超过对象的情况. (2认同)
  • @DavidSchwartz不难想象A类的实例是在`main()`中创建的,并且会比需要shared_ptr的API更长.无论如何,我认为解决方案的局限性是明确的. (2认同)

Dav*_*rtz 5

假设类A支持移动语义,请执行以下操作:

std::shared_ptr<A> newA = make_shared<A> (std::move (_a));
Run Code Online (Sandbox Code Playgroud)

不要再使用_a了,只能使用newA.您现在可以传递newA给该功能.

如果类A不支持移动语义,则没有安全/理智的方法来执行此操作.任何黑客只会发生工作,并可能在未来破裂.如果您控制了足够的类代码,则可以添加对移动语义的支持.

但据我所知,仍然创建了一个新的A实例.

你为什么在乎?您要避免的是复制实例中的所有数据,这样做.

移动语义的关键是数据从一个实例移动到另一个实例,而不必进行分配/复制/释放.当然,这会使原始实例"空",所以不要再使用它了.