2014-09-04 46 views
4
#include <cstdlib> 
struct B { 
    virtual void f(); 
    void mutate(); 
    virtual ~B(); 
}; 
struct D1 : B { void f(); }; 
struct D2 : B { void f(); }; 
void B::mutate() { 
    new (this) D2; // reuses storage — ends the lifetime of *this 
    f(); // undefined behavior - WHY???? 
    ... = this; // OK, this points to valid memory 
} 

我需要解释为什么f() invokation有UB? new (this) D2;重用存储,但它也调用D2的构造函数,并自启动新对象的生命周期。 f()等于this -> f()这就是我们只需拨打D2的成员函数f()谁知道它为什么是UB?重用新对象的存储开始生命周期吗?

+0

Placement-new应该在大多数派生类中使用,并将它们替换为相同类型的对象。这是您拥有UB的另一个原因,因为您不仅要替换基类子对象,而且要用不同类型的对象替换它。 – 0x499602D2 2014-09-04 04:51:04

+0

@ 0x499602D2标准的18.6.1.3定义了这样的位置的行为 - 新的,但没有说任何关于假设在大多数派生类中使用**以及创建相同类型的对象** – 2014-09-04 04:55:06

+4

3.8“如果在一个对象的生命周期已经结束,一个新的对象被创建在原始对象所占据的存储位置,[...]原始对象的名称将自动引用新对象[...]用于在以下情况下操作新对象:原始对象是类型T的派生对象(1.8),新对象是类型T的派生对象(即,它们不是基类子对象)。 – 0x499602D2 2014-09-04 04:58:02

回答

1

该标准列出了该示例§3.8 67 N3690:

struct C { 
    int i; 
    void f(); 
    const C& operator=(const C&); 
}; 

const C& C::operator=(const C& other) { 
    if (this != &other) { 
    this->~C(); // lifetime of *this ends 
    new (this) C(other); // new object of type C created 
    f(); // well-defined 
    } 
    return *this; 
} 

C c1; 
C c2; 
c1 = c2; // well-defined 
c1.f(); // well-defined; c1 refers to a new object of type C 

注意,这个例子是就地建设新对象之前终止对象的生命周期(与你的代码,这不叫析构函数)。

但即使你这样做了,标准也说:

如果一个对象的生命周期结束之后和存储 从而占领被重用或释放的对象,新的对象是 前在原始对象占用的存储位置创建一个 指向原始对象的指针,引用 为原始对象,或者原始对象的名称将为 自动引用新对象,并且一旦 新对象的生命周期已经开始,可以用来操纵n EW对象,如果:

- 为新对象存储恰好覆盖其原始对象占据,和存储位置 - 新的对象是 相同类型与原始对象的(忽略顶层 cv-qualifiers)和

- 原始对象的类型不是 const限定的,并且如果类类型不包含任何非静态的 数据成员,其类型是const限定的或引用类型和

- 原始对象是一个最派生的对象(1.8)类型T和 新对象是类型T的最派生对象(即,它们不是 基类子对象)。

注意'和'字样,上述条件必须全部满足。

既然你没有满足所有的条件(你有一个派生类对象中,放入一个基类对象的内存空间),你有未定义行为与隐性或显性利用这个参考的东西时,指针。

取决于编译器实现,这可能或可能现在吹因为基类的虚拟对象保留用于虚表就地构建,其覆盖了一个派生类型的对象的某些虚拟函数是指一些空间vtable可能与不同,将对齐问题和其他低级内部问题放在一起,你会发现简单的sizeof不足以确定你的代码是否正确。

+0

你是说他没有终止旧对象的生命?标准说他是。 – 2014-09-06 15:58:22

+0

他并没有破坏这个物体,这可能是非常糟糕的。为了正确,我会指定这个,谢谢 – 2014-09-06 16:05:00

1

此结构是很有意思:

  • 放置新不保证调用对象的析构函数。所以这段代码不会正确地确保对象的生命周期结束。

  • 所以原则上你应该在重用对象之前调用析构函数。但是,你会继续执行一个死的对象的成员函数。根据标准部分。9.3.1/2 如果为非X类型的对象或从X派生的类型调用类X的非静态成员函数,则行为未定义。

  • 如果不明确地删除你的对象,当你在你的代码做,你再重新创建一个新的对象(构建第二个B没有destoying第一位的,然后D2这个新B的OT顶部)。

当你的新对象的创建完成后,在执行功能时,您当前对象的身份实际上已经改变了。你不能确定是否在放置之前读取了将被调用的虚拟函数的指针 - 新的(因此是指向D1 :: f的旧指针)或之后(因此D2 :: f)。

顺便说一下,正是出于这个原因,对于在union中可以或不可以做什么有一些限制,其中为不同的活动对象共享相同的存储空间(参见第9.5/2点和特别是标准中的9.5/4)。

+1

Placement new不会清理旧对象,但会杀死它。一个对象的生命周期结束,如果它的内存被重用。 – 2014-09-06 15:57:18

+0

是的,真实的:一旦被覆盖,旧的物体将不再显示生命的迹象!然而,*“在对象的生命周期结束之后,在对象占用的存储器被重用之前”强烈建议原则上,生命的终结应该在重用之前发生,9.5/4点在另一个上下文中证明了这一点。这就是为什么我制定*“不会**正确**确保”*。例如,如果对象使用智能指针,则会直接覆盖这些指针,并且它们的引用计数将会错误:使用对象可能会被杀死,但它仍然会困扰代码! – Christophe 2014-09-06 16:35:14

+0

“*在对象的生命周期结束之后,在对象占用的存储被重用之前*”是析构函数运行时说的一种奇特的方式,如果您选择运行*。在对象的生命周期结束之后,在对象占用的存储之前重新使用或释放​​之前,在原始对象占据的存储位置创建新的对象*“是对内存事实的点头不会消失。你可以用'free()'释放它,但是你的libc仍然拥有它,并且会通过'malloc()'把它给回给你。 – 2014-09-06 16:36:13