Неправильно ли разыменовывать указатель, чтобы получить ссылку?
Я бы предпочел использовать ссылки везде, но в тот момент, когда вы используете контейнер STL, вам нужно использовать указатели, если вы действительно не хотите передавать сложные типы по значению. И я чувствую себя грязным, обращаясь к ссылке, это просто кажется неправильным.
Это?
Чтобы уточнить...
MyType *pObj = ...
MyType &obj = *pObj;
Разве это не "грязно", так как вы можете (хотя бы в теории, так как сначала проверите его) разыщите указатель NULL?
EDIT: О, и вы не знаете, были ли объекты динамически созданы или нет.
Ответы
Ответ 1
Убедитесь, что указатель не равен NULL, прежде чем вы попытаетесь преобразовать указатель в ссылку и что объект останется в области действия до тех пор, пока ваша ссылка будет (или останется выделенной в отношении кучи) будет в порядке и морально чист:)
Ответ 2
Инициализация ссылки с помощью разыменованного указателя абсолютно прекрасна, ничего плохого в этом не происходит. Если p
- указатель, и если разыменование является допустимым (например, оно не равно null), то *p
- это объект, на который указывает. Вы можете привязать ссылку к этому объекту так же, как вы привязываете ссылку на любой объект. Очевидно, что вы должны убедиться, что ссылка не переживает объект (как любая ссылка).
Так, например, предположим, что я передал указатель на массив объектов. Это может быть также пара итераторов или вектор объектов или объект map
, но я буду использовать массив для простоты. Каждый объект имеет функцию order
, возвращающую целое число. Я должен вызвать функцию bar
один раз для каждого объекта, чтобы увеличить значение order
:
void bar(Foo &f) {
// does something
}
bool by_order(Foo *lhs, Foo *rhs) {
return lhs->order() < rhs->order();
}
void call_bar_in_order(Foo *array, int count) {
std::vector<Foo*> vec(count); // vector of pointers
for (int i = 0; i < count; ++i) vec[i] = &(array[i]);
std::sort(vec.begin(), vec.end(), by_order);
for (int i = 0; i < count; ++i) bar(*vec[i]);
}
Ссылка, которую инициализировал мой пример, - это параметр функции, а не переменная напрямую, но я мог бы просто сделать это:
for (int i = 0; i < count; ++i) {
Foo &f = *vec[i];
bar(f);
}
Очевидно, что vector<Foo>
будет некорректным, так как тогда я буду называть bar
копией каждого объекта по порядку, а не по каждому объекту в порядке. bar
принимает неконстантную ссылку, так что в стороне от производительности или чего-либо еще, что явно было бы неправильным, если bar
изменяет ввод.
Вектор интеллектуальных указателей или вектор указателя ускорения также будет неправильным, так как я не владею объектами в массиве и, конечно же, не должен их освобождать. Сортировка исходного массива также может быть запрещена или, если возможно, невозможна, если она map
, а не массив.
Ответ 3
Нет. Как еще вы могли бы реализовать operator=
? Вы должны разыменовать this
, чтобы вернуть ссылку себе.
Обратите внимание, что я все равно сохраняю элементы в контейнере STL по значению - если ваш объект не огромен, накладные расходы на распределение кучи будут означать, что вы используете больше хранилища и менее эффективны, чем вы если вы просто сохранили элемент по значению.
Ответ 4
Мой ответ напрямую не затрагивает вашу первоначальную проблему, но кажется, что вы столкнулись с этой проблемой, потому что у вас есть контейнер STL, в котором хранятся типы указателей.
Boost предоставляет библиотеку ptr_container для решения этих ситуаций. Например, ptr_vector
внутренне сохраняет указатели на типы, но возвращает ссылки через свой интерфейс. Обратите внимание, что это означает, что контейнер владеет указателем на экземпляр и будет управлять его удалением.
Вот краткий пример, демонстрирующий это понятие.
#include <string>
#include <boost/ptr_container/ptr_vector.hpp>
void foo()
{
boost::ptr_vector<std::string> strings;
strings.push_back(new std::string("hello world!"));
strings.push_back(new std::string());
const std::string& helloWorld(strings[0]);
std::string& empty(strings[1]);
}
Ответ 5
Я бы предпочел использовать ссылки везде, но в тот момент, когда вы используете контейнер STL, вам нужно использовать указатели, если вы действительно не хотите передавать сложные типы по значению.
Просто, чтобы быть ясным: контейнеры STL были разработаны для поддержки определенной семантики ( "семантика значений" ), например "элементы в контейнере могут быть скопированы". Поскольку ссылки не являются перегруппированными, они не поддерживают семантику значений (т.е. Пытаются создать std::vector<int&>
или std::list<double&>
). Вы правы, что не можете помещать ссылки в контейнеры STL.
Как правило, если вы используете ссылки вместо простых объектов, вы либо используете базовые классы, либо хотите избежать нарезки, либо пытаетесь избежать копирования. И да, это означает, что если вы хотите хранить элементы в контейнере STL, вам понадобится использовать указатели, чтобы избежать нарезки и/или копирования.
И да, законно (хотя в данном случае это не очень полезно):
#include <iostream>
#include <vector>
// note signature, inside this function, i is an int&
// normally I would pass a const reference, but you can't add
// a "const* int" to a "std::vector<int*>"
void add_to_vector(std::vector<int*>& v, int& i)
{
v.push_back(&i);
}
int main()
{
int x = 5;
std::vector<int*> pointers_to_ints;
// x is passed by reference
// NOTE: this line could have simply been "pointers_to_ints.push_back(&x)"
// I simply wanted to demonstrate (in the body of add_to_vector) that
// taking the address of a reference returns the address of the object the
// reference refers to.
add_to_vector(pointers_to_ints, x);
// get the pointer to x out of the container
int* pointer_to_x = pointers_to_ints[0];
// dereference the pointer and initialize a reference with it
int& ref_to_x = *pointer_to_x;
// use the reference to change the original value (in this case, to change x)
ref_to_x = 42;
// show that x changed
std::cout << x << '\n';
}
О, и вы не знаете, были ли объекты динамически созданы или нет.
Это не важно. В приведенном выше примере x
находится в стеке, и мы сохраняем указатель на x
в pointers_to_vectors
. Несомненно, pointers_to_vectors
использует внутренний динамически выделенный массив (и delete[]
этот массив, когда vector
выходит за пределы области видимости), но этот массив содержит указатели, а не указания на вещи. Когда pointers_to_ints
выходит из области видимости, внутренний int*[]
равен delete[]
-ed, но int*
не delete
d.
Это фактически упрощает использование указателей с контейнерами STL, поскольку контейнеры STL не будут управлять временем жизни объектов с указателем. Вы можете посмотреть библиотеку контейнеров указателей Boost. В противном случае вы либо (1) хотите использовать контейнеры STL для интеллектуальных указателей (например, boost:shared_ptr
, которые являются законными для контейнеров STL), или (2) управлять другим временем жизни объектов с указателем. Вы уже можете делать (2).
Ответ 6
Если вы хотите, чтобы в контейнере содержались объекты, которые динамически распределены, вам не следует использовать необработанные указатели. Используйте unique_ptr
или любой другой подобный тип.
Ответ 7
В этом нет ничего плохого, но, пожалуйста, имейте в виду, что на уровне машинного кода ссылка обычно совпадает с указателем. Таким образом, обычно указатель на самом деле не разыменован (без доступа к памяти) при назначении ссылки.
Таким образом, в реальной жизни эта ссылка может быть 0, и авария возникает при использовании ссылки - что может произойти намного позже ее назначения.
Конечно, то, что происходит в точности, зависит от версии компилятора и аппаратной платформы, а также от параметров компилятора и точного использования ссылки.
Официально поведение разыменования 0-указателя undefined и, следовательно, все может случиться. Это что-то включает в себя то, что он может немедленно сработать, но также и то, что он может сбой значительно позже или никогда.
Поэтому всегда удостоверяйтесь, что вы никогда не назначаете 0-указатель на ссылку - ошибки очень трудно найти.
Изменить: Создал "обычно" курсив и добавил абзац об официальном поведении "undefined".