Скажем, есть объект A, которому принадлежит объект B через std::unique_ptr<B>
, Далее B содержит необработанную указатель (слабую) ссылку на A. Тогда деструктор A вызовет деструктор B, поскольку он владеет им.
Каким будет безопасный способ доступа к A в деструкторе B? (так как мы также можем быть в деструкторе A).
Безопасным способом для меня было бы явным образом сбросить сильную ссылку на B в деструкторе A, чтобы B уничтожался предсказуемым образом, но какова общая лучшая практика?
Я не адвокат по языку, но думаю, что все в порядке. Вы ступаете на опасную почву и, возможно, должны пересмотреть свой дизайн, но если вы будете осторожны, я думаю, вы можете просто полагаться на тот факт, что участники уничтожаются в обратном порядке, они были объявлены.
Так что это нормально
#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
пытается использовать это.
Каким будет безопасный способ доступа к 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 или один из подобъектов базового класса,
поведение не определено.
Как уже упоминалось, «безопасного пути» не существует. На самом деле, как было указано 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
, Не делайте при любых обстоятельствах проектируйте это как сетевой протокол! Это потребует намного больше защитных ограждений.
Учти это:
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 согласно приведенному выше примеру.
Если вы посмотрите только на отношения двух классов 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.