Передача указателя "this" другому классу / функции в деструкторе

Является ли законным C++ для создания рабочего объекта в стеке в деструкторе некоторого главного объекта и передачи this указатель мастер-объекта на вспомогательный объект? Затем вспомогательный объект будет также вызывать функции-члены главного объекта или обращаться к переменным-членам.

Другими словами, является ли следующий законный C++?

struct MasterClass
{
  MasterClass (int data);

  ~MasterClass ();

  int data;
};

struct WorkerClass
{
  WorkerClass (MasterClass *m) : m (m) { }

  void do_some_work () { m->data = 42; }

  MasterClass *m;
};

MasterClass::MasterClass (int data)
: data (data)
{ }

MasterClass::~MasterClass ()
{
  WorkerClass w (this);

  w.do_some_work ();
}

int main ()
{
  MasterClass m (7);
}

Я понимаю, что время жизни мастер-объекта заканчивается, как только деструктор начинает выполняться. Но я считаю, что законно вызывать не виртуальные функции-члены в деструкторе любого объекта, который использует неявный this Аргумент / параметр.

2 ответа

Решение

И да и нет.

Да, потому что это законно в этом очень коротком примере, который вы показали.

Нет, потому что это может привести к UB, есть некоторые предостережения, связанные с использованием объекта во время уничтожения

TLDR Всегда хорошо, если у тебя нет наследства.

Теперь для случаев, когда использование объекта во время уничтожения не подходит.

В следующих случаях предполагается, что следующее уже написано

struct V;
struct A;
struct B;
struct D;

void foo(A* a = nullptr);

struct V {
    virtual void f();
    virtual void g();
};

struct A : virtual V {
    virtual void f();
};

struct B : virtual V {
    virtual void g();
    ~B() {
        foo();
    }
};

struct D : A, B {
    virtual void f();
    virtual void g();
    ~D() {
        foo(this);
    }
};

int main() {
    D d;
}

Вызов виртуальных функций

После уничтожения x (иначе, как только его деструктор называется)

Если вызов виртуальной функции использует явный доступ к члену класса, а выражение объекта ссылается на полный объект x или один из подобъектов базового класса этого объекта, но не x или один из его подобъектов базового класса, поведение не определено.

Это означает, что если вы используете явный доступ к члену класса для вызова виртуальной функции с указателем, указывающим на всю x, но почему-то указатель не тип x ни его основы, поведение не определено.

void foo(A* a) {
    static auto ptr = a;
    ptr->g();  // UB when called from ~B
               // ptr refers to B, but is neither B nor its base
}

С помощью typeid

Если операнд typeid относится к строящемуся или разрушаемому объекту, а статический тип операнда не является ни классом конструктора или деструктора, ни одной из его основ, поведение не определено.

Аналогично, если операнд относится к разрушаемому объекту, но каким-то образом не является объектом и его основами, поведение не определено.

void foo(A* a) {
    static auto ptr = a;
    typeid(*ptr);  // UB when called from ~B()
                   // ptr refers to B, but is neither B nor its base
}

С помощью dynamic_cast

Если операнд dynamic_­cast относится к строящемуся или разрушаемому объекту, а статический тип операнда не является указателем или объектом собственного класса конструктора или деструктора или одной из его баз, dynamic_­cast приводит к неопределенному поведению.

Та же сделка

void foo(A* a) {
    static auto ptr = a;
    dynamic_cast<B*>(ptr); // UB when called from ~B()
                           // ptr refers to B, but is neither B nor its base
}

Заключение

Теперь, если вы думаете, что это фиаско и не поняли, что происходит, просто не проходите мимо this где-нибудь в деструкторе.

Все цитаты из http://eel.is/c++draft/class.cdtor

Да, это законно, поскольку главный объект не будет уничтожен до прекращения выполнения деструктора.

Тем не менее, это не очень хорошая практика в целом.

Другие вопросы по тегам