умные указатели — Доступ к владельцу в деструкторе Stack Overflow

Скажем, есть объект A, которому принадлежит объект B через std::unique_ptr<B>, Далее B содержит необработанную указатель (слабую) ссылку на A. Тогда деструктор A вызовет деструктор B, поскольку он владеет им.

Каким будет безопасный способ доступа к A в деструкторе B? (так как мы также можем быть в деструкторе A).

Безопасным способом для меня было бы явным образом сбросить сильную ссылку на B в деструкторе A, чтобы B уничтожался предсказуемым образом, но какова общая лучшая практика?

8

Решение

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

Так что это нормально

#include <iostream>

struct Noisy {
int i;
~Noisy() { std::cout << "Noisy " << i << " dies!" << "\n"; }
};

struct A;

struct B {
A* parent;
~B();
B(A& a) : parent(&a) {}
};

struct A {
Noisy n1 = {1};
B     b;
Noisy n2 = {2};
A() : b(*this) {}
};

B::~B() { std::cout << "B dies. parent->n1.i=" << parent->n1.i << "\n"; }

int main() {
A a;
}

Живая демо.

так как члены A разрушены в порядке n2 затем b затем n1, Но это не хорошо

#include <iostream>

struct Noisy {
int i;
~Noisy() { std::cout << "Noisy " << i << " dies!" << "\n"; }
};

struct A;

struct B {
A* parent;
~B();
B(A& a) : parent(&a) {}
};

struct A {
Noisy n1 = {1};
B     b;
Noisy n2 = {2};
A() : b(*this) {}
};

B::~B() { std::cout << "B dies. parent->n2.i=" << parent->n2.i << "\n"; }

int main() {
A a;
}

Живая демо.

поскольку n2 к тому времени уже был разрушен B пытается использовать это.

3

Другие решения

Каким будет безопасный способ доступа к A в деструкторе B? (так как мы также можем быть в деструкторе A).

Там нет безопасного пути:

3.8 / 1

[…] Время жизни объекта типа T заканчивается, когда:

— если T является типом класса с нетривиальным деструктором (12.4), вызов деструктора начинается […]

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

РЕДАКТИРОВАТЬ: Как Крис Дрю написал в комментарии вы Можно использовать объект после того, как его деструктор начал, извините, моя ошибка, я пропустил одно важное предложение в стандарте:

3.8 / 5

До начала жизни объекта, но после хранения, которое объект будет занимать, было
выделено или, после окончания срока службы объекта и до хранения, которое занимает объект
повторно использованный или освобожденный, любой указатель, который указывает на место хранения, где объект будет или был расположен
могут быть использованы, но только в ограниченном количестве. О строящемся или разрушаемом объекте см. 12.7.. Иначе,
такой указатель относится к выделенному хранилищу (3.7.4.2) и использует указатель, как если бы указатель имел тип void *,
хорошо определен. Такой указатель может быть разыменован, но результирующее lvalue может использоваться только в ограниченном
способы, как описано ниже. Программа имеет неопределенное поведение, если:
[…]

В 12.7 есть список вещей, которые вы можете сделать во время строительства и разрушения, некоторые из наиболее важных:

12.7 / 3:

Чтобы явно или неявно преобразовать указатель (глвэу) ссылаясь на объект класса X на указатель (ссылка)
в
прямой или косвенный Базовый класс B X, построение X и построение всех его прямых или
должны начаться косвенные основания, которые прямо или косвенно вытекают из B, и уничтожение этих классов не должно быть завершено, в противном случае преобразование приводит к неопределенному поведению. к сформировать указатель на (или
получить доступ к значению) прямого нестатического члена объекта obj
, должно начаться строительство объекта
а также его уничтожение не должно быть завершено, в противном случае вычисление значения указателя (или доступ к
значение элемента) приводит к неопределенному поведению.

12.7 / 4

Функции-члены, включая виртуальные функции (10.3), можно вызвать во время строительство или разрушение (12.6.2).
Когда виртуальная функция вызывается прямо или косвенно из конструктора или из деструктора, в том числе
во время создания или уничтожения нестатических членов данных класса, а также объекта, к которому
call apply — объект (назовите его x), находящийся в процессе строительства или уничтожения, вызываемая функция — окончательное переопределение
в классе конструктора или деструктора, а не в классе, переопределяющем его в более производном классе. Если виртуальный
вызов функции использует явный доступ к члену класса (5.2.5), а выражение объекта относится к полному
объект x или один из подобъектов базового класса этого объекта, но не x или один из подобъектов базового класса,
поведение не определено.

3

Как уже упоминалось, «безопасного пути» не существует. На самом деле, как было указано PcAF, время жизни A к тому времени, когда вы достигнете Bдеструктор.
Я также хочу отметить, что это действительно хорошая вещь! Должен быть строгий порядок уничтожения объектов.
Теперь, что вы должны сделать, это сказать B заранее тот A собирается разрушиться.
Это так же просто, как

void ~A( void ) {
b->detach_from_me_i_am_about_to_get_destructed( this );
}

Проходя this указатель может быть необходимым или нет в зависимости от дизайна объекта B (Если B содержит много ссылок, возможно, потребуется знать, какой из них отсоединить. Если он содержит только один, то this указатель лишний).
Просто убедитесь, что соответствующие функции-члены являются частными, чтобы интерфейс мог использоваться только по назначению.

замечание:
Это простое и легкое решение, которое подойдет, если вы сами полностью контролируете связь между A а также B, Не делайте при любых обстоятельствах проектируйте это как сетевой протокол! Это потребует намного больше защитных ограждений.

0

Учти это:

struct b
{
b()
{
cout << "b()" << endl;
}
~b()
{
cout << "~b()" << endl;
}
};

struct a
{
b ob;
a()
{
cout << "a()" << endl;
}
~a()
{
cout << "~a()" << endl;
}
};

int main()
{
a  oa;
}

//Output:
b()
a()
~a()
~b()

«Тогда деструктор A вызовет деструктор B, поскольку он владеет им». Это не правильный способ вызова деструкторов в случае составных объектов. Если вы видите выше пример, то сначала a разрушается, а затем b разрушается. aдеструктор не вызовет bдеструктор, так что контроль вернется к aдеструктор.

«Какой будет безопасный способ доступа к А в деструкторе Б?». Согласно приведенному выше примеру a поэтому уже уничтожен a недоступен в bдеструктор.

«так как мы можем быть в деструкторе А).». Это не правильно. Опять же, когда контроль выходит из aдеструктор тогда только контроль входит bдеструктор.

Деструктор является функцией-членом класса T. Как только элемент управления выходит из деструктора, к классу T нельзя получить доступ. Все члены-данные класса T могут быть доступны в конструкторах и деструкторах класса T согласно приведенному выше примеру.

0

Если вы посмотрите только на отношения двух классов A и B, конструкция будет хорошо:

class A {
B son;
A(): B(this)  {}
};
class B {
A* parent;
B(A* myparent): parent(myparent)  {}
~B() {
// do not use parent->... because parent's lifetime may be over
parent = NULL;    // always safe
}
}

Проблемы возникают, если объекты A и B распространяются на другие программные модули. Затем вы должны использовать инструменты из std :: memory, такие как std :: shared_ptr или std: weak_ptr.

-2
По вопросам рекламы [email protected]