Разве неправильно разыменовывать указатель, чтобы получить ссылку? - PullRequest
31 голосов
/ 10 августа 2010

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

Это так?

Чтобы уточнить ...

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

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

РЕДАКТИРОВАТЬ: О, и вы не знаете, были ли объекты созданы динамически или нет.

Ответы [ 7 ]

17 голосов
/ 10 августа 2010

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

13 голосов
/ 10 августа 2010

Инициализация ссылки с разыменованным указателем - это абсолютно нормально, в этом нет ничего плохого.Если p является указателем, и если разыменование является допустимым (например, оно не равно нулю), то *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, а не массив.

4 голосов
/ 10 августа 2010

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

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

2 голосов
/ 10 августа 2010

Я бы предпочел использовать ссылки везде, но как только вы используете контейнер 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[] s, который массив, когда vector выходит из области видимости), но этот массив содержит указатели, а не наведенные объекты.Когда pointers_to_ints выпадает из области видимости, внутренний int*[] является delete[] -ed, но int* s не delete d.

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

2 голосов
/ 10 августа 2010

Мой ответ не имеет прямого отношения к вашей первоначальной проблеме, но, похоже, вы столкнулись с этой проблемой, потому что у вас есть контейнер 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]);
}
1 голос
/ 10 августа 2010

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

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

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

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

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

1 голос
/ 10 августа 2010

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

...