Объекты С++: когда следует использовать указатель или ссылку

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

Когда я должен использовать каждый из них? Какова практическая разница?

Ни один из этих вопросов не ответил на мои сомнения:

Ответ 1

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

Он очень похож на квалификатор const: язык может существовать без него, он просто присутствует как бонусная функция, которая упрощает разработку безопасного кода.

Ответ 2

"Я должен удалить и указать, что они остаются, пока их область не закончится."

Нет, это совершенно неправильно.

Объекты, выделенные с помощью new, должны быть удалены [*]. Объекты, не выделенные с помощью new, не должны удаляться. Возможно иметь указатель на объект, который не был выделен с помощью new, и возможно иметь ссылку на объект, который был выделен с помощью new.

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

[*] с delete. Массив, выделенный с помощью new[], должен быть удален с помощью delete[]. Существуют доступные инструменты, которые могут помочь отслеживать выделенные ресурсы и делать эти вызовы для вас, называемые интеллектуальными указателями, поэтому довольно редко нужно явно делать вызов самостоятельно, а не просто организовывать его, но тем не менее он должно быть сделано.

Ответ 3

У вас много ситуаций, когда параметр не существует или является недопустимым, и это может зависеть от семантики кода выполнения. В таких ситуациях вы можете использовать указатель и установить его в NULL (0), чтобы сигнализировать об этом состоянии. Кроме того,

  • Указатель может быть переназначен новым государство. Ссылка не может. Это желательно в некоторых ситуациях.
  • Указатель помогает передать семантику владельца. Это особенно полезно в многопоточной среде, если состояние параметра используется для выполнения в отдельный поток, и вы обычно не проводите опрос до выхода потока. Теперь поток может удалить его.

Ответ 4

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

Если возможно использовать ссылки, используйте указатели, только если вы не можете их избежать.

Еще короче: предпочитайте ссылки по указателям.

Ответ 5

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

Когда вы создаете указатель с new, память для него зарезервирована и сохраняется до тех пор, пока вы не назовете delete на нем, но срок службы идентификатора по-прежнему ограничивается концом блока кода. Если вы создаете объекты в функции и добавляете их во внешний список, объекты могут оставаться в памяти в памяти после возвращения функции, и вы все равно можете ссылаться на них без идентификатора.

Здесь (упрощенный) пример из Umbra, рамки С++, которую я разрабатываю. Там содержится список модулей (указателей на объекты), хранящихся в движке. Механизм может добавить объект к этому списку:

void UmbraEngine::addModule (UmbraModule * module) {
    modules.push(module);
    module->id = modules.size() - 1;
}

Получить одно:

UmbraModule * UmbraEngine::getModule (int id) {
    for (UmbraModule **it=modules.begin(); it != modules.end(); it++) {
        if ((*it)->id == id) return *it;
    }
}

Теперь я могу добавлять и получать модули, не зная их идентификаторов:

int main() {
    UmbraEngine e;
    for (int i = 0; i < 10; i++) {
        e.addModule(new UmbraModule());
    }
    UmbraModule * m = e.getModule(5); //OK
    cout << m << endl; //"0x127f10" or whatever
    for (int j = 0; k < 10; j++) {
        UmbraModule mm; //not a pointer
        e.addModule(&mm);
    }
    m = e.getModule(15);
    cout << m << endl; //{null}
}

Список модулей сохраняется на протяжении всей продолжительности программы, мне не нужно заботиться о продолжительности жизни модулей, если они создаются с помощью new:). Так что в основном это - с указателями, вы можете иметь долгоживущие объекты, которым когда-либо не нужен идентификатор (или имя, если хотите), чтобы ссылаться на них:).

Еще один приятный, но очень простой пример:

void getVal (int * a) {
    *a = 10;
}
int main() {
    int b;
    getVal(&b);
    return b;
}

Ответ 6

Эмм... не совсем. Это IDENTIFIER, который имеет область действия. Когда вы создаете объект с помощью new, но его область видимости заканчивается, вы можете потерять память (или нет - зависит от того, чего вы хотите достичь) - объект находится в памяти, но у вас нет средств ссылаясь на него больше.

Разница в том, что указатель - это адрес в памяти, поэтому, если у вас есть, скажем, этот код:

int * a = new int;

a - указатель. Вы можете распечатать его - и вы получите что-то вроде "0x0023F1" - это просто это: адрес. Он не имеет значения (хотя некоторое значение сохраняется в памяти по этому адресу).

int b = 10;

b - это переменная со значением 10. Если вы ее распечатаете, вы получите 10.

Теперь, если вы хотите, чтобы a указывал на адрес b, вы можете сделать:

a = &b; //a points to b address

или если вы хотите, чтобы адрес, указанный a, имел значение b:

*a = b; //value of b is assigned to the address pointed by a

Пожалуйста, скомпилируйте этот образец и прокомментируйте/раскомментируйте строки 13 и 14, чтобы увидеть разницу (обратите внимание, где указаны идентификаторы и WHAT VALUE). Я надеюсь, что результат будет понятным.

#include <iostream>

using namespace std;

int main()
{
    int * a = new int;
    int b = 10;
    cout << "address of a: " << a << endl;
    cout << "address of b: " << &b << endl;
    cout << "value of a: " << *a << endl;
    cout << "value of b: " << b << endl;
    a = &b; //comment/uncomment
    //*a = b; //comment/uncomment
    cout << "address of a: " << a << endl;
    cout << "address of b: " << &b << endl;
    cout << "value of a: " << *a << endl;
    cout << "value of b: " << b << endl;
}

Ответ 7

Сначала ответьте на последний вопрос. Тогда первый вопрос будет иметь больше смысла.

Q: "В чем практическая разница [между указателем и ссылкой]?"

A: Ссылка - это только локальный псевдоним для другой переменной. Если вы передадите параметр по ссылке, то этот параметр будет точно такой же переменной, как тот, который был указан в вызывающем операторе. Однако внутри обычно нет разницы между указателем и ссылкой. Ссылки предоставляют "синтаксический сахар", позволяя вам уменьшить количество ввода, которое вы должны делать, когда все, что вам действительно нужно, - это доступ к одному экземпляру заданной переменной.

Q: "Когда я должен использовать каждый из них?"

A: Это будет вопрос личных предпочтений. Вот основное правило, за которым я следую. Если мне понадобится манипулировать переменной в другой области видимости, и эта переменная является либо внутренним типом, либо классом, который должен использоваться как собственный тип (т.е. std::string и т.д.)), Либо класс const экземпляр, затем я передаю по ссылке. В противном случае я пройду по указателю.

Ответ 8

Дело в том, что вы не можете восстановить ссылку на другой объект. Ссылки связаны временем компиляции и не могут быть нулевыми или отскоками. Поэтому указатели не являются излишними, если вы сомневаетесь в том, что:)

Ответ 9

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

В то время как указатели могут быть перенаправлены в другое место, ссылки как постоянные указатели не могут быть перенаправлены. Поэтому ссылки can not могут использоваться для перемещения массивов в функциях и т.д.

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

Я также прочитал, что время обработки ссылок меньше,

как

int и я = b;

i ++; занимает меньше времени, чем

int * j = b;

(* j) ++;

но я еще должен подтвердить это. Если кто-то может пролить свет на это утверждение, было бы здорово.

Комментарии приветствуются:)