1

I was checking some code and I encountered the following scenario. It's working fine but it sounds like an undefined behaviour to me but I don't know what to search and how to prove it.

#include <memory>
#include <functional>
#include <string>
#include <iostream>

class Child
{
public:
    Child()
    {
        std::cout << "Child created\n";
    }
    ~Child()
    {
        std::cout << "Child is dead\n";
    }
};

class Parent
{
    std::unique_ptr<Child> m_child;
public:
    using Callback = std::function<void()>;
    Parent(const Callback& killMe)
    {
        std::cout << "Parent created\n";
        killMe();
        m_child = std::make_unique<Child>();
    }

    ~Parent()
    {
        std::cout << "Parent is dead\n";
    }
};

class GrandParent
{
    std::unique_ptr<Parent> m_child;
public:
    GrandParent()
    {
        m_child = std::make_unique<Parent>([this]() { KillChild(); });
    }

    void KillChild()
    {
        m_child.reset();
    }
};

int main()
{
    {
        GrandParent gp;
    }
    return 0;
}

I expected that, Parent get killed before creation of Child but apparently it's not the case, I tried it on multiple compilers and always got the following output :

Parent created
Child created
Parent is dead
Child is dead
HMD
  • 2,202
  • 6
  • 24
  • 37
  • What's that killMe() function ? – Pierre Podevin Dec 05 '19 at 09:15
  • 1
    _"...For both user-defined or implicitly-defined destructors, after the body of the destructor is executed, the compiler calls the destructors for all non-static non-variant members of the class,..."_ see __Destruction sequence__ here: https://en.cppreference.com/w/cpp/language/destructor So after `Parent::~Parent()` has been executed the members of `Parent` are destroyed. If `killMe()` does anything then leaving it's definition out of the question is a mistake. – Richard Critten Dec 05 '19 at 09:16
  • 1
    @PierrePodevin The callback function which passed to `Parent` by `GrandParent`. – HMD Dec 05 '19 at 09:17
  • re question title: where is the delete in the constructor? Have you left some code out of the question? – Richard Critten Dec 05 '19 at 09:36
  • @RichardCritten `Parent` class constructor. – HMD Dec 05 '19 at 09:38
  • No `delete` there - please post a [mcve] so we don't have to guess what unseen code does and how it does it. – Richard Critten Dec 05 '19 at 09:39
  • 1
    @RichardCritten `Grandparent` has a pointer to `Parent`. When constructing `Parent`, it passes a callback to `Grandparent::KillChild`, which will call `delete`(in `reset()`) on `Parent` object that is currently being constructed. This already is minimal and reproducible. – Yksisarvinen Dec 05 '19 at 09:40
  • See https://stackoverflow.com/questions/3150942/is-delete-this-allowed So __UB__ as the code accesses a member after the delete. – Richard Critten Dec 05 '19 at 09:42
  • 1
    `delete this` question has good reasoning why there is UB (accessing members after delete), but I wonder if calling destructor from within the constructor is actually defined behaviour. – Yksisarvinen Dec 05 '19 at 09:56
  • 2
    What are you trying to achieve? It only "works" because `m_child.reset()` is called when `m_child` isn't assigned yet, so becomes a no-op. Otherwise `killMe()` would delete the instance and the subsequent `m_child = ...` would access a deleted object. – rustyx Dec 05 '19 at 10:14
  • @rustyx I'm sorry misread your comment the first time but now I think you are completely right. I didn't look at it that way! I would accept it if you post it as answer. – HMD Dec 05 '19 at 10:29

1 Answers1

4

The code has no UB only because it doesn't work as intended.

    m_child = std::make_unique<Parent>([this]() { KillChild(); })

Here

  1. A instance of Parent is constructed
    • which calls killMe() which calls KillChild()
    • KillChild() calls m_child.reset(), which does nothing because m_child is empty
  2. then m_child is assigned

If it did work as intended, you'd get a use-after-free after killMe():

killMe();
m_child = std::make_unique<Child>(); // is actually this->m_child = , but *this* is gone now
rustyx
  • 80,671
  • 25
  • 200
  • 267