1
  • 以下代码是否具有已定义的行为?
  • 如果不是,代码的哪一部分是 UB 以及标准的哪一部分说明它是 UB ?
  • 如果此代码是 UB,是否有任何 [微小] 更改可以修复它?
  • 如果没有什么可以解决它,还有什么其他的代码方案/模式可以用来实现相同的功能?

class C
{
public:
    virtual ~C() {}
    virtual void switch_me() = 0;
};

class C1 : public C
{
public:
    C1() : b(true)      { std::cout << "C1\n"; }
    ~C1()               { std::cout << "~C1\n"; }
private:
    void switch_me();
    bool b;
};

class C2 : public C
{
public:
    C2() : i(1)         { std::cout << "C2\n"; }
    ~C2()               { std::cout << "~C2\n"; }
private:
    void switch_me();
    int  i;
};

void C1::switch_me()
{
    this->~C1();            // lifetime of *this ends here
    std::cout << "blih\n";  // execute some code that does
                            // not attempt to access object
    new(this) C2();         // create a C2 instance in-place
}

void C2::switch_me()
{
    this->~C2();            // lifetime of *this ends here
    std::cout << "blah\n";  // execute some code...
    new(this) C1();         // create a C1 instance in-place
}

class Cnt
{
public:
    Cnt()           { new(&storage) C1(); }
    ~Cnt()          { (*this)->~C(); }
    C* operator->() { return reinterpret_cast<C*>(&storage); }
private:
    char storage[std::max(sizeof(C1),sizeof(C2))];
};

int main()
{
    Cnt c;
    c->switch_me();
    c->switch_me();
    return 0;
}
4

1 回答 1

3

您没有关于switch_me函数的未定义行为:销毁后您不会以任何方式访问对象,并且下一次访问发生在新对象上。如果您保存指针和对C返回 vy 的对象的引用operator->并在调用switch_meper 3.8/7后使用它,您可能有 UB :

如果在对象的生命周期结束之后,在对象占用的存储空间被重用或释放之前,在原始对象占用的存储位置创建一个新对象,一个指向原始对象的指针,一个指向原始对象的引用引用原始对象,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,可用于操作新对象,如果:

  • 新对象的存储恰好覆盖了原始对象占用的存储位置,并且
  • 新对象与原始对象的类型相同(忽略顶级 cv 限定符),并且
  • 原始对象的类型不是 const 限定的,并且,如果是类类型,则不包含其类型为 const 限定或引用类型的任何非静态数据成员,并且
  • 原始对象是 T 类型的最派生对象(1.8),而新对象是 T 类型的最派生对象(也就是说,它们不是基类子对象)。

您在其他地方确实有 UB,即您的存储。它的对齐比您要放置在其中的对象弱,这可能会导致对齐问题。使用alignas关键字指定所需的对齐方式:

alignas(C1) alignas(C2) char storage[std::max(sizeof(C1),sizeof(C2))];

如果将两个对齐说明符应用于同一声明,则使用较大的。

于 2016-06-11T09:54:45.547 回答