如何防止在堆上创建对象?

32

有人知道我如何在平台无关的C++代码中防止对象在堆上被创建吗?也就是说,对于一个类"Foo",我想要防止用户这样做:

Foo *ptr = new Foo;

并且只允许他们做这件事:

Foo myfooObject;

有没有人有任何想法?

谢谢,


2
你为什么想要这样做? - David Rodríguez - dribeas
1
反过来,这也可能对读者有趣:https://dev59.com/c3VD5IYBdhLWcg3wAGeD - kevinarpe
9个回答

29

Nick的回答是一个不错的起点,但不完整,因为你实际上需要重载:

private:
    void* operator new(size_t);          // standard new
    void* operator new(size_t, void*);   // placement new
    void* operator new[](size_t);        // array new
    void* operator new[](size_t, void*); // placement array new

(良好的编码实践应该建议您也重载delete和delete[]运算符--尽管我会这样做,但由于它们不会被调用,所以并不是真正必要的。)

Pauldoo 也正确指出,这种方法无法在Foo聚合时生效,虽然在从Foo继承时可以。您可以使用一些模板元编程魔术来帮助防止这种情况发生,但它不免受到"恶意用户"的影响,因此可能不值得增加复杂性。文档说明如何使用,以及代码审查以确保其正确使用,是唯一 ~100% 的方法。


1
一个私有构造函数与一个公共静态工厂方法(返回值)结合起来会实现相同的功能吗? - kevinarpe
1
@kevinarpe 这取决于我们如何理解问题。如果按照问题中的确切代码“Foo myfooObject;”那么它不会被编译。话虽如此,如果我想要控制对象的创建方式,我更偏向于像你所建议的方式。 - Patrick Johnmeyer
1
请注意,可以通过使用::new而不是new来规避此问题,因为这将在全局范围内执行对operator new的查找。 - Brian Bi
这是否意味着您永远无法在堆上创建 Foo 对象? - baltermia

11

你可以对Foo重载new并将其设置为私有。这意味着编译器会抱怨...除非你正在从Foo中的堆上创建Foo的实例。为了捕捉这种情况,你可以简单地不编写Foo的new方法,然后链接器会抱怨未定义的符号。

class Foo {
private:
  void* operator new(size_t size);
};

PS. 是的,我知道这可以很容易地被规避。我真的不推荐这样做 - 我认为这是一个坏主意 - 我只是在回答问题!;-)


8
我不知道如何可靠且便携地完成它,但是......
如果对象在栈上,则您可能可以在构造函数中断言“this”的值始终接近堆栈指针。如果是这种情况,那么对象很有可能在堆栈上。
我认为并不是所有平台都按相同的方向实现它们的堆栈,因此您可能希望在应用程序启动时进行一次单独的测试,以验证堆栈增长的方式。或者做一些调整:
FooClass::FooClass() {
    char dummy;
    ptrdiff_t displacement = &dummy - reinterpret_cast<char*>(this);
    if (displacement > 10000 || displacement < -10000) {
        throw "Not on the stack - maybe..";
    }
}

我认为无论这个和虚拟变量是在堆还是栈中,它们总是会紧密相连的。 - Vargas
1
@Vargas - 我不同意。'dummy'始终会在堆栈上,因为它是一个自动本地变量。this指针可以指向堆栈(如果FooClass用作本地变量),也可以指向堆(如果FooClass分配在堆上或在堆上分配了一个类,则聚合)。 - pauldoo
你是对的,我把 dummy 错误地当成了一个成员变量... 抱歉。 - Vargas

4

@Nick

这个问题可以通过创建一个从Foo派生或聚合的类来规避。我认为我所建议的方法(虽然不够健壮)仍然适用于派生和聚合类。

例如:

struct MyStruct {
    Foo m_foo;
};

MyStruct* p = new MyStruct();

在堆上创建了一个 'Foo' 的实例,绕过了 Foo 的隐藏 new 操作符。


3
由于调试头可以覆盖运算符new的签名,因此最好使用...签名作为完整的解决方案:
private:
void* operator new(size_t, ...) = delete;
void* operator new[](size_t, ...) = delete;

0

这可以通过将构造函数设为私有并提供一个静态成员在堆栈中创建对象来防止。

Class Foo
{
    private:
        Foo();
        Foo(Foo& );
    public:
        static Foo GenerateInstance() { 
            Foo a ; return a; 
        }
}

这将使对象的创建始终在堆栈中进行。


0
你可以在Foo类中声明一个名为“operator new”的函数,它将阻止访问new的正常形式。
这是你想要的行为吗?

0
你可以将它声明为一个接口,从而更直接地控制实现类的代码。

-1

不确定这是否提供任何编译时机会,但您是否考虑过重载类的 'new' 运算符?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接