Обнаруживать или избегать мертвых ссылок на временные во время компиляции
Следующие ошибки в программе минимальной длины при компиляции с -O3
и, возможно, с -O2
, но хорошо работает с -O0
(с лязгом 4.0):
#include <iostream>
class A {
public:
virtual void me() const { std::cerr << "hi!\n"; }
};
class B {
public:
B(const A& a_) : a(a_) {}
virtual void me() const { a.me(); }
private:
const A& a;
};
class C {
public:
C(const B& b_) : b(b_) {}
void me() const { b.me(); }
public:
const B& b;
};
int main() {
C c = C(A());
c.me();
}
Причина в том, что c.b
инициализируется со ссылкой на временный объект класса B
который построен из временного A
, После конструктора c.C()
выходы временные B
нет, но ссылка на него остается в c.b
,
Какие хорошие практики я могу использовать, чтобы избежать этой ситуации, учитывая, что я не контролирую реализацию B
или же A
? Существуют ли статические анализаторы, способные обнаружить это состояние? (Моя версия scan-build
не нашел проблемы.)
Связанный: Обнаружение свисающих ссылок на временные
2 ответа
Мне лично не нравится иметь переменные-члены в качестве ссылок. Они не могут быть скопированы или перемещены, и пользователь класса должен убедиться, что объект переживает саму ссылку. Исключением может быть внутренний вспомогательный класс, предназначенный для использования в качестве только временных объектов, таких как функторы.
Замена ссылки указателем должна быть простой, а затем, если вы также используете указатель в конструкторе:
class C {
public:
C(const A *a_) : a(a_) {}
private:
const A *a;
};
...и так далее. Если класс очень большой, и вы чувствуете себя ленивым и не хотите менять члена, вы можете просто изменить конструктор:
class C {
public:
C(const A *a_) : a(*a_) {}
private:
const A &a;
};
Чтобы неправильно использовать этот класс, как говорит ОП, вам нужно написать что-то вроде:
C c = C(&A());
И тогда ошибка должна быть очевидна: указатель на временный указатель - очень плохая идея!
PS: я бы добавил explicit
для вашего конструктора, просто чтобы он не участвовал в автоматических преобразованиях, которые, я думаю, являются частью вашей проблемы.
Я бы получил отдельные классы от B
а также C
(возможно, даже используя шаблонный класс).
Эти классы будут содержать нереферентный член, который становится a
а также b
Ссылаться на.
Затем я бы реализовал необходимые конструкторы копирования / операторы присваивания в этих производных классах, чтобы предотвратить висячие ссылки.
(Тогда у меня был бы крепкий разговор с автором B
а также C
).