Краткий вопросник по C++. Часть 1 (FAQ)

Источник: hardline
Автором английской версии является Marshall Cline (cline@parashift.com), автором перевода - Ярослав Миронов (slava_mironov@mail.ru)

Этот документ является переводом сборника ответов на часто задаваемые вопросы группы новостей comp.lang.c++.

[8.1] Что такое ссылка?

Ссылка - это псевдоним (другое имя) для объекта.

Ссылки часто используются для передачи параметра по ссылке:

    void swap(int& i, int& j)
    {
        int tmp = i;
        i = j;
        j = tmp;
    }

    int main()
    {
        int x, y;
        // ...
        swap(x,y);
    }

В этом примере i и j - псевдонимы для переменных x и y функции main . Другими словами, i - это x . Не указатель на x и не копия x , а сам x . Все, что вы делаете с i , проделывается с x , и наоборот.

Вот таким образом вы как программист должны воспринимать ссылки. Теперь, рискуя дать вам неверное представление, несколько слов о том, каков механизм работы ссылок. В основе ссылки i на объект x - лежит, как правило, просто машинный адрес объекта x . Но когда вы пишете i++ , компилятор генерирует код, который инкрементирует x . В частности, сам адрес, который компилятор использует, чтобы найти x , остается неизменным. Программист на С может думать об этом, как если бы использовалась передача параметра по указателю, в духе языка С, но, во-первых, & (взятие адреса) было бы перемещено из вызывающей функции в вызываемую, и, во-вторых, в вызываемой функции были бы убраны * (разыменование). Другими словами, программист на С может думать об i как о макроопределении для (*p) , где p - это указатель на x (т.е., компилятор автоматически разыменовывает подлежащий указатель: i++ заменяется на (*p)++ , а i = 7 на *p = 7 ).

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

[8.2] Что происходит в результате присваивания ссылке?

Вы меняете состояние ссыльного объекта (того, на который ссылается ссылка).

Помните: ссылка - это сам объект, поэтому, изменяя ссылку, вы меняете состояние объекта, на который она ссылается. На языке производителей компиляторов ссылка - это lvalue (left value - значение, которое может появиться слева от оператора присваивания).

[8.3] Что происходит, когда я возвращаю из функции ссылку?

В этом случае вызов функции может оказаться с левой стороны оператора (операции) присваивания.

На первый взгляд, такая запись может показаться странной. Например, запись f() = 7 выглядит бессмысленной. Однако, если a - это объект класса Array , для большинства людей запись a[i] = 7 является осмысленной, хотя a[i] - это всего лишь замаскированный вызов функции Array::operator[](int) , которая является оператором обращения по индексу для класса Array :

    class Array {
    public:
        int size() const;
        float& operator[] (int index);
        // ...
    };

    int main()
    {
        Array a;
        for (int i = 0; i < a.size(); ++i)
            a[i] = 7; // В этой строке вызывается Array::operator[](int)
    }

[8.4] Как можно переустановить ссылку, чтобы она ссылалась на другой объект?

Невозможно в принципе.

Невозможно отделить ссылку от ее объекта.

В отличие от указателя, ссылка, как только она привязана к объекту, не может быть "перенаправлена" на другой объект. Ссылка сама по себе ничего не представляет, у нее нет имени, она сама - это другое имя для объекта. Взятие адреса ссылки дает адрес объекта, на который она ссылается. Помните: ссылка - это объект, на который она ссылается.

С этой точки зрения, ссылка похожа на const указатель [ 18.5 ], такой как int* const p (в отличие от указателя на const [ 18.4 ], такого как const int* p ). Несмотря на большую схожесть, не путайте ссылки с указателями - это не одно и то же.

[8.5] В каких случаях мне стоит использовать ссылки, и в каких - указатели?

Используйте ссылки, когда можете, а указатели - когда это необходимо.

Ссылки обычно предпочтительней указателей, когда вам ненужно их "перенаправлять" [ 8.4 ]. Это обычно означает, что ссылки особенно полезны в открытой (public) части класса. Ссылки обычно появляются на поверхности объекта, а указатели спрятаны внутри.

Исключением является тот случай, когда параметр или возвращаемый из функции объект требует выделения "охранного" значения для особых случаев. Это обычно реализуется путем взятия/возвращения указателя, и обозначением особого случая при помощи передачи нулевого указателя (NULL). Ссылка же не может ссылаться на разыменованный нулевой указатель.

Примечание: программисты с опытом работы на С часто недолюбливают ссылки, из-за того что передача параметра по ссылке явно никак не обозначается в вызывающем коде. Однако с обретением некоторого опыта работы на С++, они осознают, что это одна из форм сокрытия информации, которая является скорее преимуществом, чем недостатком. Т.е., программисту следует писать код в терминах задачи, а не компьютера (programmers should write code in the language of the problem rather than the language of the machine).

РАЗДЕЛ [9]: Встроенные ( inline ) функции

[9.1] Что такое встроенная функция?

Встроенная функция - это функция, код которой прямо вставляется в том месте, где она вызвана. Как и макросы, определенные через #define , встроенные функции улучшают производительность за счет стоимости вызова и (особенно!) за счет возможности дополнительной оптимизации ("процедурная интеграция").

[9.2] Как встроенные функции могут влиять на соотношение безопасности и скорости?

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

К сожалению, этот метод идет вразрез с безопасностью типов, а также требует вызова функции для доступа к любым полям структуры (если вы позволили бы прямой доступ, то его мог бы получить кто угодно, поскольку будет известно, как интерпретировать данные, на которые указывает void* . Такое поведение со стороны пользователя приведет к сложностям при последующем изменении структуры подлежащих данных).

Стоимость вызова функции невелика, но дает некоторую прибавку. Классы С++ позволяют встраивание функций, что дает вам безопасность инкапсуляции вместе со скоростью прямого доступа. Более того, типы параметры встраиваемых функций проверяются компилятором, что является преимуществом по сравнению с (?)сишными #define макросами.

[9.3] Зачем мне использовать встроенные функции? Почему не использовать просто #define макросы?

Поскольку #define макросы опасны [ 9.3 ], опасны [ 34.1 ], опасны [ 34.2 ], опасны [ 34.3 ].

В отличие от #define макросов, встроенные ( inline ) функции неподвержены известным ошибкам двойного вычисления, поскольку каждый аргумент встроенной функции вычисляется только один раз. Другими словами, вызов встроенной функции - это то же самое что и вызов обычной функции, только быстрее:

	// Макрос, возвращающий модуль (абсолютное значение) i
	#define unsafe(i) 		( (i) >= 0 ? (i) : -(i) )

	// Встроенная функция, возвращающая абсолютное значение i
	inline
	int safe(int i)
	{
		return i >= 0 ? i : -i;
	}

	int f();

	void userCode(int x)
	{
		int ans;
		ans = unsafe(x++); // Ошибка! x инкрементируется дважды
		ans = unsafe(f()); // Опасно! f() вызывается дважды
		ans = safe(x++); // Верно! x инкрементируется один раз
		ans = safe(f()); // Верно! f() вызывается один раз
	}

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

Макросы вредны для здоровья; не используйте их, если это не необходимо.

Читать часть 2


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=21601