2
class base {
    int a;
protected:
    template<class T>
    class derived;
public:
    base() {}
    virtual ~base() {}
    virtual void func() {}
    static base* maker();
};

template <class T>    
class base::derived 
    : public base
{
public: 
    derived() {}
    virtual ~derived() {}
    virtual void func() {
        this->~derived(); //<--is this legal?
        new (this) derived<int>(); //<--is this legal?
    }
};

base* base::maker() {
    return new derived<double>();
}

int main() {
    base* p = base::maker(); //p is derivedA<double>
    p->func(); //p is now derivedA<int>
    delete p; //is the compiler allowed to call ~derived<double>()?
}

这是我的代码的简短、独立、正确(可编译)的示例(基本上是any_iterator为了我自己的成长而重新发明)。

问题归结为:当共享基础上没有任何其他成员时,使用从同一基础虚拟派生的不同类型进行销毁this和重建是否是未定义的行为?具体来说,是否允许this编译器调用静态类型的跟踪,或者这在技术上是不符合标准的?

[编辑] 一些人指出,如果derivedA在堆栈上创建,编译器可能会调用不正确的析构函数。(1) 我在标准中找不到任何允许编译器这样做的东西。(2) 这与我的问题的意图不同,所以我更改了代码以显示derived 不能放在堆栈上。 base虽然仍然可以在堆栈上。

4

2 回答 2

3

我认为这显然不行。

作为前言,对象的生命周期确实可以通过调用析构函数来结束,但是您只能(并且必须)在其位置构造相同类型的新对象:

{
  Foo x;
  x.~Foo();
  ::new (&x) Foo;
}  // x.~Foo() must be a valid call here!

请记住,在作用域结束时将调用析构函数!

但是在您的情况下,您正在构建一个完全不同的对象:

::new (&x) Bar;   // Bar = DerivedA<int>

显然如果sizeof(Bar)超过sizeof(Foo)这个就不行了。

(也许如果您可以对对象大小以及对齐保证做出额外保证,我们可以进一步考虑这一点。)

更新:即使你在想,好吧,所以这些类型是从同一个基派生的,所以调用析构函数会带来虚拟的快乐,我仍然很确定这是违规行为。在这个静态设置中,编译器可以很好地静态解析虚拟调用,所以如果你改变了&x.

更新2:关于同一件事的另一个想法:静态类型*&x已知的,我认为你必须尊重这一点。换句话说,编译器没有理由考虑局部变量的静态类型发生变化的可能性。

于 2011-11-18T00:03:18.197 回答
1

我很确定这不是有效的代码,原因如下:

  1. 如果插入的类型大小不同,就会发生不好的事情(我不太确定,但我认为标准并没有对类型的大小做出太多承诺,所以从理论上讲,这可能很难证明它们具有相同的大小(通过在实践中他们很可能会))
  2. 如果变量的类型是静态已知的(可能是由于它是在堆栈上构造的,但理论上它可以做同样的事情,如果它可以看到分配并证明指针不能被修改)编译器可以随意静态解析虚拟方法调用(例如析构函数)并使用它们,这显然会破坏代码
  3. 即使变量的类型不是静态已知的,我也很确定编译器可以假设它的类型在它的生命周期内不会改变(指针不能在函数内部改变,所以它应该能够假设指向类型也没有)。因此,虽然它不能静态解析方法,但它可能会重用以前调用虚拟方法(例如更改类型的方法)中的 vmt 指针

编辑:现在我想这不会违反严格的别名规则,因为在放置 new 之后 this 指向不兼容的类型?授予它不会再次在函数中显式访问,但我认为不能保证编译器不会插入访问(尽管极不可能)。无论如何,这意味着编译器可以假设这种行为不会发生。

编辑:在查看新的 C++ 标准时,我发现 [basic.life] (§3.8.5) 给出了与未定义行为示例基本相同的内容(它实际上并没有破坏对象,但我没有不知道如何使事情变得更好):

#include<cstdlib>
structB{
    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
    ...=this; //OK, this points to valid memory
}
void g(){
    void* p = std::malloc(sizeof(D1) + sizeof(D2));
    B* pb = new(p)D1;
    pb->mutate();
    &pb; //OK: pb points to valid memory
    void* q = pb; //OK: pb points to valid memory
    pb->f(); //undefined behavior, lifetime of *pb hasended
}

这应该证明这是不允许的。

于 2011-11-18T02:33:47.087 回答