Разве неправильно разыменовать указатель для получения ссылки?

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

Это?

Разъясниться...

MyType *pObj = ...
MyType &obj = *pObj;

Разве это не 'грязно', так как Вы можете (даже если бы только в теории, так как Вы проверили бы его сначала) разыменовывают Нулевого указателя?

Править: О, и Вы не знаете, были ли объекты динамично созданы или нет.

31
задан Steve Guidi 9 August 2010 в 23:20
поделиться

7 ответов

Убедитесь, что указатель не равен NULL, прежде чем пытаться преобразовать указатель в ссылку, и что объект будет оставаться в области действия до тех пор, пока ваша ссылка будет (или останется выделенной по отношению к куча), и будешь в порядке, и морально чистым :)

18
ответ дан 27 November 2019 в 22:35
поделиться

Инициализация ссылки разыменованным указателем - это абсолютно нормально, ничего плохого в этом нет. Если p - это указатель, и если разыменование его корректно (например, он не null), то *p - это объект, на который он указывает. Вы можете связать ссылку на этот объект точно так же, как вы связываете ссылку на любой объект. Очевидно, вы должны убедиться, что ссылка не переживет объект (как и любая ссылка).

Так, например, предположим, что мне передан указатель на массив объектов. С таким же успехом это может быть пара итераторов, или вектор объектов, или карта объектов, но для простоты я буду использовать массив. У каждого объекта есть функция 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 был бы неправильным, поскольку тогда я бы вызывал bar на copy каждого объекта по порядку, а не на каждом объекте по порядку. bar принимает неконстантную ссылку, поэтому, не считая производительности или чего-то еще, это явно будет неправильно, если bar изменит входные данные.

Вектор умных указателей, или вектор указателей boost, также будет неправильным, поскольку я не владею объектами в массиве и, конечно, не должен их освобождать. Сортировка исходного массива также может быть запрещена или, вообще, невозможна, если это map, а не массив.

13
ответ дан 27 November 2019 в 22:35
поделиться

Нет. Как еще вы можете реализовать operator=? Вы должны разыменовать this, чтобы вернуть ссылку на себя.

Обратите внимание, что я бы все же хранил элементы в контейнере STL по значению - если только ваш объект не огромен, накладные расходы на выделение кучи будут означать, что вы используете больше памяти и менее эффективны, чем если бы вы просто хранили элемент по значению.

4
ответ дан 27 November 2019 в 22:35
поделиться

Мой ответ не относится непосредственно к вашему первоначальному вопросу, но, похоже, вы столкнулись с этой проблемой, потому что у вас есть контейнер 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]);
}
2
ответ дан 27 November 2019 в 22:35
поделиться

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

1
ответ дан 27 November 2019 в 22:35
поделиться

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

Для ясности: контейнеры STL были разработаны для поддержки определенной семантики («семантики значений»), например, «элементы в контейнере можно копировать». Поскольку ссылки не могут быть повторно привязаны, они не поддерживают семантику значений (т.е. попробуйте создать std :: vector или std :: list ). Вы правы, что не можете помещать ссылки в контейнеры 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 . Конечно, pointer_to_vectors использует динамически распределенный массив внутри (и delete [] s этот массив, когда вектор выходит за пределы области видимости), но этот массив содержит указатели, а не объекты, на которые они указывают.Когда pointers_to_ints выпадает из области видимости, внутренний int * [] имеет значение delete [] -ed, но int * являются не удалить d.

Это, по сути, затрудняет использование указателей с контейнерами STL, поскольку контейнеры STL не могут управлять временем жизни указанных объектов. Вы можете посмотреть библиотеку контейнеров указателей Boost. В противном случае вы либо (1) захотите использовать контейнеры STL интеллектуальных указателей (например, boost: shared_ptr , что является допустимым для контейнеров STL), либо (2) управлять временем жизни указанных объектов некоторыми другими способ. Возможно, вы уже делаете (2).

2
ответ дан 27 November 2019 в 22:35
поделиться

В этом нет ничего плохого, но имейте в виду, что на уровне машинного кода ссылка обычно такая же, как указатель. Итак, обычно указатель на самом деле не разыменовывается (нет доступа к памяти) при назначении ссылки. Таким образом, в реальной жизни ссылка может быть равна 0, и при использовании ссылки происходит сбой - что может произойти намного позже, чем ее присвоение.

Конечно, что именно происходит, сильно зависит от версии компилятора и аппаратной платформы, а также от параметров компилятора и точного использования ссылки.

Официально поведение разыменования 0-указателя undefined , и поэтому может случиться что угодно. Это что-нибудь включает в себя то, что сбой может произойти немедленно, но также что сбой может произойти намного позже или никогда.

Поэтому всегда убедитесь, что вы никогда не назначаете 0-указатель ссылке - такие ошибки очень сложно найти.

Редактировать: Выделено «обычно» курсивом и добавлен абзац об официальном «неопределенном» поведении.

1
ответ дан 27 November 2019 в 22:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: