通过make_shared停止堆分配

Stop heap allocation via make_shared

本文关键字:分配 shared make 通过      更新时间:2023-10-16

我想强制我的对象在堆栈上,以强制执行非常严格的语义并解决一些生命周期问题。我读了几篇关于如何做到这一点的文章,并将operator new设置为私有(或删除)。当直接使用new时,这似乎可以正常工作,但make_shared编译得很好。

#include <boost/smart_ptr.hpp>
class A
{
private:
   void *operator new( size_t );
   void operator delete( void* );
   void *operator new[]( size_t );
   void operator delete[]( void* );
};
int main()
{
//  A* a = new A;      // Correctly produces compile error
    boost::shared_ptr<A> a2 = boost::make_shared<A>();
}

直接使用new A会像预期的那样给我这个错误:

error: ‘static void* A::operator new(size_t)’ is private

我猜make_shared正在工作,因为它正在使用放置新操作符,但我找不到任何讨论如何禁止这种情况的文章。我想到的最佳解决方案是显式地删除make_shared的模板专门化

namespace boost
{
    template<>
    shared_ptr<A> make_shared<A>() = delete;
};

这显然是boost::make_shared特有的。这真的是最好的方法吗?

new的位置形式很容易处理——它们只是附带了额外的参数。例如,简单的放置表单是

void* operator new(std::size_t, void*);

注意18.6.1.3禁止在全局范围内重定义这些;但是,为您的特定类型重新定义(或删除/使不可访问)它们应该没有问题。

不幸的是,make_shared使用了::new (pv) T(std::forward<Args>(args)...)的作用域。正如我提到的,你不允许乱动全局布局new。因此,您无法在编译时阻止它,并且任何运行时陷阱都将是一种攻击(检查this指针以查看它是否在堆栈的边界内)。

不能通过使任何操作符仅不可访问来强制类的对象始终位于堆栈上:任何可以在堆栈上构造的对象也可以作为成员嵌入到另一个对象中。即使您的原始类可能会反对在堆上分配,但包含类不会。我认为这就是boost::make_shared()的情况:在内部,它可能分配一些记录,其中包含其管理数据和实际分配的对象。或者,它可以使用来自某种分配器的分配函数,该分配器不映射到类型的operator new(),而是使用自己的operator new()重载。

我不确定是否有可能阻止堆分配(至少,当对象嵌入到另一个对象中时),但是这样做的任何方法都需要使构造函数不可访问(最有可能的是private)并使用某种工厂函数,可能与移动相结合。另一方面,如果你可以移动一个对象,那么你就有了一个可访问的构造函数,并且没有什么可以阻止这个对象被移动到堆上的一个对象中。

如果你特别想防止对具体类型使用std::make_shared()(或boost::make_shared(),尽管我不能引用后者专门化的规则),你可以专门化std::make_shared():如果模板包含用户定义的类型,则允许用户专门化任何模板(除非另有说明)。因此,您可以防止Astd::make_shared()一起使用:

class A
{
public:
    A();
    A(int);
};
namespace std
{
    template <> std::shared_ptr<A> make_shared<A>() = delete;
    template <> std::shared_ptr<A> make_shared<A, int>(int&&) = delete;
}
namespace boost
{
    template <> boost::shared_ptr<A> make_shared<A>() = delete;
    template <> boost::shared_ptr<A> make_shared<A, int>(int&&) = delete;
}

显然,如果您在A中有多个构造函数,您可能需要添加更多的专门化. ...如果你的类型恰好是一个类模板,或者你的构造函数恰好是一个模板,那你就不走运了:你不能部分特化函数模板。

关于你关于放置new的问题(make_shared()可能使用也可能不使用):放置new(和delete)签名如下:

void* operator new(size_t, void*) noexcept;
void* operator new[](size_t, void*) noexcept;
void  operator delete(void*, void*) noexcept;
void  operator delete[](void*, void*) noexcept;

(参见18.6 [support。但是,我怀疑让它们不可访问对你有什么帮助。