1
  • Does the following code have a defined behavior ?
  • If not, what part of the code is UB and what section(s) of the standard states it is UB ?
  • If this code is UB, is there any [minor] change that can fix it ?
  • If nothing can fix it, what other code scheme/pattern could be used to implement the same feature ?

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;
}
shrike
  • 4,449
  • 2
  • 22
  • 38
  • The code for `switch_me` is **** <- Insert an expletive here. Why are you doing this? – Ed Heal Jun 11 '16 at 09:04
  • @πάντα ῥεῖ: definitely not a duplicate ! in my question, a member function calls ITS OWN CLASS DESTRUCTOR (ie: it's a self destruction). – shrike Jun 11 '16 at 09:11
  • @shrike I don't see any fundamental difference. Read both of the answers. The self destruction of placement `new`ed instances is even mentioned there. – πάντα ῥεῖ Jun 11 '16 at 09:12
  • @πάντα ῥεῖ : there's a strong difference, though... is a virtual member function code allowed to execute although its object lifetime has ended ? Can you answer this question reading the other question you refered to as a duplicate to mine ? – shrike Jun 11 '16 at 09:18
  • 1
    You do not call any member functions or access object in any way between destruction and construction, so virtual member functions are not related to your question. However you are likely to run into aligment issues: `storage` is not aligned at all. – Revolver_Ocelot Jun 11 '16 at 09:21
  • @shrike Well, you're suspecting too much UB in your question where there isn't (as was explained in the dupe). I don't get your concerns about virtual function calls. – πάντα ῥεῖ Jun 11 '16 at 09:25
  • @Revolver: and this is intentional... if I did, the code would be UB, for sure. However, this does not ensure that this code is not UB. If you think it's not, please answer. – shrike Jun 11 '16 at 09:25
  • @πάντα ῥεῖ: not sure this is obvious... what I do in Cnt may be UB: suppose, instead of `operator->()`, I define: `C& operator*() { return *reinterpret_cast(&storage); }` then use the returned ref in successive calls to `switch_me()`; AFAIU, this would be UB. – shrike Jun 11 '16 at 09:32
  • 2
    @shrike *If nothing can fix it, what other code scheme/pattern could be used to implement the same feature* -- Instead of posting obscure C++ code, what problem are you really trying to solve? – PaulMcKenzie Jun 11 '16 at 09:34
  • 1
    @PaulMcKenzie: what is obscure ? it's quite clearly a revisited state pattern, and I want to know if this implementation is UB or not... – shrike Jun 11 '16 at 09:41

1 Answers1

3

You do not have Undefined Behavior regarding switch_me function: you do not access object in any way after destruction, and next access happens on new object. You might have UB if you save pointer and reference to C object returned vy operator-> and use it after call to switch_me per 3.8/7:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
  • the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

You do have UB in other place, namely, your storage. It has weaker alignment than object you want to place in it, and that can cause alignment issues. Use alignas keyword to specify desired alignment:

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

If two alignment specifiers are applied to same declaration, larger is used.

Revolver_Ocelot
  • 8,609
  • 3
  • 30
  • 48
  • Thanks for your valuable answer. In a previous design, I defined in `Cnt` an `operator*()` returning a reference to storage; as you noticed in your answer, this was UB and I wanted to make sure the new design is not, using `operator->()`. Regarding alignment, in the real code, i am using `std::aligned_storage<>` instead of `char[]`, so yes, in my snippet it's UB, but not in the real code. I'll probably accept your answer if nobody else notices something else UB somewhere... Anyway, thanks. – shrike Jun 11 '16 at 10:51