通过make_shared停止堆分配

时间:2012-12-25 01:51:10

标签: c++ c++11

我想强制我的对象在堆栈上强制执行非常严格的语义并解决一些生命周期问题。我已经阅读了几篇关于如何执行此操作的文章,并将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正在使用,因为它正在使用placement new运算符,但我找不到任何讨论如何禁止此操作的文章。我提出的最佳解决方案是明确删除make_shared

的模板专门化
namespace boost
{
    template<>
    shared_ptr<A> make_shared<A>() = delete;
};

这显然对boost::make_shared非常具体。这真的是最好的方式吗?

2 个答案:

答案 0 :(得分:4)

new的展示位置形式非常容易处理 - 它们只是附带了额外的参数。例如,简单的展示位置表单是

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

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

不幸的是,make_shared使用范围::new (pv) T(std::forward<Args>(args)...)。正如我所提到的,你不可能搞乱新的全球布局。因此,您无法在编译时阻止它,并且任何运行时陷阱都是一个hack(检查this指针以查看它是否在堆栈的入口内。)

答案 1 :(得分:2)

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

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

如果您特别希望阻止将std::make_shared()用于具体类型(或boost::make_shared(),尽管我不能引用专门针对后者的规则),您可以专门化std::make_shared() :根据17.6.4.2.1 [namespace.std]第1段,如果用户涉及用户定义的类型,则允许用户专门化任何模板(除非另有说明)。因此,您可以阻止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.dynamic]第1段)。我怀疑让它们无法访问会对你有所帮助。