Я провел небольшой эксперимент, пытаясь понять ссылки в C++:
#include <iostream>
#include <vector>
#include <set>
struct Description {
int a = 765;
};
class Resource {
public:
Resource(const Description &description) : mDescription(description) {}
const Description &mDescription;
};
void print_set(const std::set<Resource *> &resources) {
for (auto *resource: resources) {
std::cout << resource->mDescription.a << "\n";
}
}
int main() {
std::vector<Description> descriptions;
std::set<Resource *> resources;
descriptions.push_back({ 10 });
resources.insert(new Resource(descriptions.at(0)));
// Same as description (prints 10)
print_set(resources);
// Same as description (prints 20)
descriptions.at(0).a = 20;
print_set(resources);
// Why? (prints 20)
descriptions.clear();
print_set(resources);
// Object is written to the same address (prints 50)
descriptions.push_back({ 50 });
print_set(resources);
// Create new array
descriptions.reserve(100);
// Invalid address
print_set(resources);
for (auto *res : resources) {
delete res;
}
return 0;
}
https://godbolt.org/z/TYqaY6Tz8
Я не понимаю, что здесь происходит. Я нашел этот отрывок из Часто задаваемые вопросы по С++:
Important note: Even though a reference is often implemented using an address in the underlying assembly language, please do not think of a reference as a funny looking pointer to an object. A reference is the object, just with another name. It is neither a pointer to the object, nor a copy of the object. It is the object. There is no C++ syntax that lets you operate on the reference itself separate from the object to which it refers.
Это вызывает у меня некоторые вопросы. Итак, если ссылка является самим объектом, и я создаю новый объект по тому же адресу памяти, означает ли это, что ссылка «становится» новым объектом? В приведенном выше примере векторы представляют собой линейные массивы; поэтому, пока массив указывает на один и тот же диапазон памяти, объект будет действительным. Однако это становится намного сложнее, когда используются другие наборы данных (например, наборы, карты, связанные списки), потому что каждый «узел» обычно указывает на разные части памяти.
Должен ли я рассматривать ссылки как неопределенные, если исходный объект уничтожен? Если да, есть ли способ определить, что ссылка уничтожена, кроме специального механизма, который отслеживает ссылки?
Примечание. Протестировано с помощью GCC, LLVM и MSVC.
«Ссылка - это объект» - это небрежный язык, хотя имхо это лучше, чем думать о ссылках как об указателях. Ссылка на самом деле не является объектом, но вы можете думать об этом так, пока объект жив, тогда ссылка болтается.
связанные/обман: stackoverflow.com/questions/6438086/…
все еще не совсем точно, но, может быть, лучше «действительная ссылка - это объект».
«Должен ли я рассматривать ссылки как неопределенные, если исходный объект уничтожен?» Да. "есть ли способ определить, что ссылка уничтожена" Нет.
Спасибо вам всем за ответы. Моя главная проблема заключалась в понимании правильности ссылок, но теперь это намного яснее.
ИМО, примечание больше вводит в заблуждение, чем разъясняет. Возможно, вам будет легче понять, если вы забудете об этом.
@PasserBy есть ли другой справочник, к которому я могу обратиться по поводу спецификации для ссылок?
@Gasim Я не знаю хорошей книги, чтобы узнать конкретно о ссылках. Но вы можете прочитать cppreference.
Спасибо, это то, что я искал. Есть даже раздел о висячих ссылках.
обычное имя также может стать недействительным, например, когда вы явно вызываете его деструктор. псевдоним становится недействительным одновременно с псевдонимом объекта.
о, и оборванная ссылка (обычно) находится в возвращаемой локальной переменной по ссылке, которая не должна иметь ничего общего с указателем.
вы также можете увидеть en.cppreference.com/w/cpp/language/lifetime





Заметка вводит в заблуждение, трактовка ссылок как синтаксического сахара для указателей хороша как ментальная модель. Во всех случаях, когда указатель может болтаться, ссылка также будет болтаться. Доступ к оборванным указателям/ссылкам является неопределенным поведением (UB).
int* p = new int{42};
int& i = *p;
delete p;
void f(int);
f(*p); // UB
f(i); // UB, with the exact same reason
Это также распространяется на стандартные контейнеры и их правила об аннулировании указателя/ссылки. Причина любого неожиданного поведения в вашем примере — просто UB.
Это зависит от того, как вы замените объект. С размещением
newстарые ссылки относятся к новому объекту (в большинстве случаев). Если выclear()иpush_back(), это технически Undefined Behavior, посколькуclear()делает недействительными все ссылки на элементы, даже если это, скорее всего, будет выглядеть так, как будто оно работает каждый раз, когда вы пытаетесь это сделать.