在重载operator new运算符中初始化类成员是否未定义?

3

举个小例子,我正在尝试找出一个变量是否分配在堆上:

struct A
{
  bool isOnHeap;
  A () {}  // not touching isOnHeap
 ~A () {}

  void* operator new (size_t size)
  {
    A* p = (A*) malloc(size);
    p->isOnHeap = true;  // setting it to true
    return p;
  }
  void operator delete (void *p) { free(p); }
};

它给出了g++-4.5中的预期结果(对于堆栈对象会有警告)。这样做是否定义不清?


我认为那种信息不值得过度使用 new —— 那是一种相当高级的功能,使用范围有限。 - Cat Plus Plus
3
我不会进行标准潜水,但我会投赞成票,这是未定义的。 *p 还不存在,对于尚不存在的对象,能够进行的操作非常非常有限...... 访问它们的成员几乎肯定不是其中之一。 - Dennis Zickefoose
3个回答

6
在重载的operator new中不能初始化类成员,因为对象的生命周期尚未开始。只能在对象构造期间初始化成员。
您无法保证实现不会在operator new返回和对象构造开始之间擦除内存,或者在对象构造期间特意将标准指定为具有不确定值的成员(例如因为它们是POD并且没有像isOnHeap那样在构造函数中明确初始化)设置为某些值。
请注意,A具有非平凡的构造函数(它是用户声明的),因此当为对象分配存储空间时,其生命周期并不开始(ISO/IEC 14882:2003,3.8 [basic.life] / 1),如果使用指向对象存储空间的指针访问非静态数据成员,则程序的行为是未定义的(3.8 / 5)。即使A是POD类型,完成new-expression后它的值仍然是不确定的,而不一定与new-expression评估之前对象存储空间中的字节中的值相关。

1
这也困扰着我。从技术上讲,布尔成员的生命周期已经开始了,因为对于bool对象,生命周期从分配存储空间时开始。然而,整个A的生命周期还没有开始,因为构造函数不是平凡的。因此,语言规范是否说,在这种情况下,bool成员的值通常不会在A的构造函数中定义? - AnT stands with Russia
我不会说“存储已被分配”。operator new只提供内存,编译器可能在此之后对内存进行某些操作。那么像“值初始化”或“零初始化”这样的东西呢?A *a = new A和A *a = new A()呢? - Suma
@Suma:我不理解你在评论中的意思。大多数operator new只是分配存储空间,当我说“分配了存储空间”时,我指的就是这个。一个_new-expression_对已分配的存储空间的其余部分的处理取决于使用的确切_new-expression_,在某种程度上还取决于实现的细节。 - CB Bailey
我使用值初始化作为一个例子,来说明我认为“布尔成员的生命周期已经开始”的概念是错误的。但我猜想我的例子是错误的,因为只有在没有用户提供构造函数时才适用于值或零初始化。尽管如此,我认为句子“从技术上讲,布尔成员的生命周期已经开始,因为对于布尔对象,生命周期始于存储分配”的说法是不正确的,因为我认为你不能假设在“new”内部成员的生命周期已经开始,但我无法解释为什么。 - Suma
注意:我忘记标记我的原始评论不是回答你的答案,而是回答AndreyT的评论。 - Suma
1
我认为bool的生命周期是否已经开始是有争议的,但我认为当A对象最终被构造时,它将被(重新)初始化。无论这意味着它被赋予特定值还是只是现在不确定取决于所使用的确切初始化方式。一个纯POD对象不必被初始化,你可以在分配后立即写入和读取其存储,但据我所知,如果你重新初始化它(例如使用放置new),那么如果新值是不确定的,那并不意味着旧值被保留。 - CB Bailey

3
正如 Charles 所说,对象在被 new 后才开始生命周期,因此在 new 实现中设置数据是非常危险的。
另外,当你的开发人员使用类似 Lint 这样的工具时,很有可能会抱怨构造函数中成员变量 isOnHeap 没有初始化。如果某个人认为“嘿,Lint 是对的,让我们在 A 的构造函数中初始化 isOnHeap”,这将破坏你尝试实现的机制。
还有第二种情况,你可能没有想到。假设有人这样写:
class MyClass
   {
   public:
      ...
   private:
      struct A m_a;
   };

int main()
   {
   MyClass *myVariable = new MyClass();
   }

那么你的new实现将不会被调用。但是A类的实例将在堆上分配(作为MyClass实例的一部分)。

你能解释一下为什么想知道某个东西是否已经在堆上分配了吗?也许有另一个更优雅的解决方案来解决你的问题。


3
即使不考虑 operator new 本身(它是非标准的,我甚至会说很丑陋,但如果了解某个特定编译器的确切细节,它可能是可行的),仍然存在另一个问题,这使得它无用:在堆栈上分配时,您无法保证 isOnHeap 的值不为 true。堆栈未初始化,并且可以在其中找到之前执行的函数调用的任何垃圾。

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