12.16 – Анонимные объекты

Добавлено 10 июля 2021 в 17:33

В некоторых случаях переменная нам нужна только временно. Например, рассмотрим следующую ситуацию:

#include <iostream>
 
int add(int x, int y)
{
    int sum{ x + y };
    return sum;
}
 
int main()
{
    std::cout << add(5, 3) << '\n';
 
    return 0;
}

Обратите внимание, что в функции add() переменная sum на самом деле используется только как временная переменная-заполнитель. Она не очень полезна – скорее, ее единственная функция – передать результат выражения в возвращаемое значение.

На самом деле существует более простой способ написать функцию add() с использованием анонимного объекта. Анонимный объект – это, по сути, значение, у которого нет имени. Поскольку у него нет имени, на него нельзя ссылаться за пределами того места, где он был создан. Следовательно, он имеет «область видимости выражения», то есть анонимные объекты создаются, вычисляются и уничтожаются в рамках одного выражения.

Вот функция add(), переписанная с использованием анонимного объекта:

#include <iostream>
 
int add(int x, int y)
{
    return x + y; // анонимный объект создается для хранения и возврата результата x + y
}
 
int main()
{
    std::cout << add(5, 3) << '\n';
 
    return 0;
}

Когда выражение x + y вычисляется, результат помещается в анонимный объект. Затем копия анонимного объекта возвращается вызывающему по значению, и анонимный объект уничтожается.

Это работает не только с возвращаемыми значениями, но и с параметрами функции. Например, вместо этого:

void printValue(int value)
{
    std::cout << value;
}
 
int main()
{
    int sum{ 5 + 3 };
    printValue(sum);
    return 0;
}

Мы можем написать так:

int main()
{
    printValue(5 + 3);
    return 0;
}

В этом случае выражение 5 + 3 вычисляется для получения результата 8, который помещается в анонимный объект. Копия этого анонимного объекта затем передается в функцию printValue() (которая печатает значение 8), а затем уничтожается.

Обратите внимание, насколько чище это делает наш код – нам не нужно засорять код временными переменными, которые используются только один раз.

Анонимные объекты классов

Хотя наши предыдущие примеры были со встроенными типами данных, можно также создавать анонимные объекты наших собственных типов классов. Это делается путем создания объектов как обычно, но без имени переменной.

Cents cents{ 5 }; // обычная переменная
Cents{ 7 };       // анонимный объект

В приведенном выше коде Cents{7} создаст анонимный объект Cents, инициализирует его значением 7, а затем уничтожит. В этом контексте это не принесет нам много пользы. Но давайте посмотрим на пример, где это может быть полезно:

#include <iostream>
 
class Cents
{
private:
    int m_cents{};
 
public:
    Cents(int cents) { m_cents = cents; }
 
    int getCents() const { return m_cents; }
};
 
void print(const Cents &cents)
{
   std::cout << cents.getCents() << " cents\n";
}
 
int main()
{
    Cents cents{ 6 };
    print(cents);
 
    return 0;
}

Обратите внимание, что этот пример очень похож на предыдущий с использованием целых чисел. В этом случае наша функция main() передает объект Cents (с именем cents) в функцию print().

Мы можем упростить эту программу, используя анонимные объекты:

#include <iostream>
 
class Cents
{
private:
    int m_cents{};
 
public:
    Cents(int cents) { m_cents = cents; }
 
    int getCents() const { return m_cents; }
};
 
void print(const Cents &cents)
{
   std::cout << cents.getCents() << " cents\n";
}
 
int main()
{
    // Обратите внимание: теперь мы передаем анонимное значение Cents
    print(Cents{ 6 }); 
 
    return 0;
}

Как и следовало ожидать, этот код напечатает:

6 cents

Теперь давайте посмотрим на более сложный пример:

#include <iostream>
 
class Cents
{
private:
    int m_cents{};
 
public:
    Cents(int cents) { m_cents = cents; }
 
    int getCents() const { return m_cents; }
};
 
Cents add(const Cents &c1, const Cents &c2)
{
    Cents sum{ c1.getCents() + c2.getCents() };
    return sum;
}
 
int main()
{
    Cents cents1{ 6 };
    Cents cents2{ 8 };
    Cents sum{ add(cents1, cents2) };
    std::cout << "I have " << sum.getCents() << " cents.\n";
 
    return 0;
}

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

Мы можем упростить нашу программу, используя анонимные значения:

#include <iostream>
 
class Cents
{
private:
    int m_cents{};
 
public:
    Cents(int cents) { m_cents = cents; }
 
    int getCents() const { return m_cents; }
};
 
Cents add(const Cents &c1, const Cents &c2)
{
    // Инициализация списком смотрит на возвращаемый тип функции
    // и соответственно создает правильный объект.
    return { c1.getCents() + c2.getCents() }; // возвращаем анонимное значение Cents
}
 
int main()
{
    Cents cents1{ 6 };
    Cents cents2{ 8 };
    // выводим анонимное значение Cents
    std::cout << "I have " << add(cents1, cents2).getCents() << " cents.\n"; 
 
    return 0;
}

Эта версия add() работает так же, как и предыдущая, за исключением того, что в ней используется анонимное значение Cents вместо именованной переменной. Также обратите внимание, что в main() мы больше не используем именованную переменную sum в качестве временного хранилища. Вместо этого мы используем возвращаемое значение add() анонимно!

В результате наша программа короче, понятнее и, как правило, ее легче проследить (если вы понимаете концепцию).

На самом деле, поскольку cents1 и cents2 используются только в одном месте, мы можем анонимизировать код еще больше:

#include <iostream>
 
class Cents
{
private:
    int m_cents{};
 
public:
    Cents(int cents) { m_cents = cents; }
 
    int getCents() const { return m_cents; }
};
 
Cents add(const Cents &c1, const Cents &c2)
{
    return { c1.getCents() + c2.getCents() }; // возвращаем анонимное значение Cents
}
 
int main()
{
    // выводим анонимное значение Cents
    std::cout << "I have " << add(Cents{ 6 }, Cents{ 8 }).getCents() << " cents.\n";
 
    return 0;
}

Резюме

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

Однако стоит отметить, что анонимные объекты обрабатываются как r-значения (а не l-значения, у которых есть адрес), поэтому к ним применяются все правила передачи и возврата r-значений.

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

Примечание. Некоторые компиляторы, например Visual Studio, позволяют устанавливать неконстантные ссылки на анонимные объекты. Это нестандартное поведение.

Теги

C++ / CppLearnCppr-value / r-значениеДля начинающихОбучениеПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.