删除这个并放置虚拟派生类的新内容

时间:2011-11-17 23:51:58

标签: c++ templates virtual placement-new

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仍然可以在堆栈中。

2 个答案:

答案 0 :(得分:3)

我认为这显然不行。

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

{
  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静态类型是已知的,我认为你必须尊重这一点。换句话说,编译器没有理由考虑局部变量的静态类型发生变化的可能性。

答案 1 :(得分:1)

由于以下几个原因,我很确定这是无效的代码:

  1. 如果插入的类型不是同样大小会发生坏事(我不太确定,但我认为标准没有对类型的大小做出太多承诺,所以从理论上看有利于它可能很难证明它们具有相同的大小(通过实践他们最有可能))。
  2. 如果变量的类型是静态已知的(可能是因为它在堆栈上构建,但理论上它可以做同样的事情,如果它可以看到分配并证明指针不能被修改)编译器可以随意静态地解析虚方法调用(例如析构函数)并使用它们显然会破坏代码
  3. 即使变量的类型不是静态知道的,我也很确定编译器可以假设它的类型在它的生命周期内不会改变(指针不能在函数内部改变,所以它应该能够假设指向的类型也没有)。因此,虽然它无法静态解析方法,但它可能会重用以前调用虚拟方法的vmt指针(例如更改类型的方法)
  4. 编辑:现在我认为它不会破坏严格的别名规则,因为在放置后新的这指向不兼容的类型?授予它不再在函数中明确访问,但我不认为可以保证编译器不会插入访问(尽管不太可能)。无论如何,这意味着编译器可以假设不会发生这种行为。

    编辑:在查看新的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
    }
    

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