Как функция может изменить значение аргумента, переданного извне, в C++?

«Как функция может изменить значение аргумента, переданного извне, в C++?» — вопрос из категории C++ Core, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В C++ есть три основных механизма, позволяющих функции модифицировать аргумент вызывающего кода. Выбор зависит от семантики и требований к производительности.

1. Передача по указателю (унаследовано из C)

Функции передаётся адрес переменной. Для доступа к значению используется оператор разыменования *.

void modifyByPointer(int* ptr, std::string* strPtr) {
    if (ptr) { // Всегда проверяйте указатель на nullptr!
        *ptr += 100;
    }
    if (strPtr) {
        *strPtr = "Modified";
    }
}

int main() {
    int value = 5;
    std::string text = "Hello";
    modifyByPointer(&value, &text); // Явная передача адресов
    // value == 105, text == "Modified"
}

Когда использовать: При работе с унаследованным C-кодом, или когда нужно явно обозначить, что аргумент может быть невалидным (nullptr).

2. Передача по ссылке (идиоматичный C++ способ)

Функции передаётся псевдоним (алиас) переменной. Синтаксис чище, ссылка не может быть null.

void modifyByReference(int& ref, std::string& strRef) {
    ref *= 2; // Изменяет оригинальную переменную
    strRef.append(" World");
}

void constReferenceExample(const std::string& cref) {
    // cref доступна только для чтения. Попытка модификации вызовет ошибку компиляции.
    std::cout << cref.length();
}

int main() {
    int a = 10;
    std::string s = "Hello";
    modifyByReference(a, s); // Синтаксис идентичен передаче по значению
    // a == 20, s == "Hello World"
    constReferenceExample(s); // Эффективно: избегаем копирования
}

Когда использовать: Почти всегда, когда нужно модифицировать аргумент или избежать копирования больших объектов (с const).

3. Передача по ссылке на указатель или ссылке на ссылку

Позволяет изменять сам указатель или ссылку внутри функции.

void allocateArray(int*& ptrRef, size_t size) {
    delete[] ptrRef; // Безопасно освобождаем старую память
    ptrRef = new int[size]; // Меняем указатель вызывающего кода
}

void swapViaReference(int& a, int& b) { // Классический пример
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int* dynamicArray = nullptr;
    allocateArray(dynamicArray, 100); // dynamicArray теперь указывает на новый массив

    int x = 1, y = 2;
    swapViaReference(x, y); // x == 2, y == 1
    delete[] dynamicArray;
}
Сравнение: Критерий Указатель Ссылка
Синтаксис вызова func(&var) func(var)
Может быть null/неинициализирована Да Нет (должна быть привязана при создании)
Можно переназначить Да (указать на другой объект) Нет (всегда псевдоним одной переменной)
Безопасность Требует проверки на nullptr Безопаснее, компилятор гарантирует инициализацию
Идиоматичность для C++ Для опциональных параметров, динамических структур Для обязательных изменяемых параметров

Правило выбора: Используйте ссылки (&) по умолчанию для параметров, которые функция должна модифицировать. Используйте *указатели (``)**, если параметр является опциональным (может отсутствовать) или если вы работаете с динамически выделяемой памятью, которую нужно переназначить.