В некоторых случаях в языке C++ переменная может быть нам нужна только временно. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> int add(int a, int b) { int result = a + b; return result; } int main() { std::cout << add(4, 2); return 0; } |
В функции add() переменная result
используется как временная переменная. Она не выполняет особой роли, функция использует её только для возврата значения.
Есть более простой способ написать функцию add() — через анонимный объект. Анонимный объект — это значение без имени. Поскольку имени нет, то и способа ссылаться на этот объект за пределами места, где он создан — тоже нет. Следовательно, анонимные объекты имеют область видимости выражения и они создаются, обрабатываются и уничтожаются в пределах одного выражения.
Вот функция add(), приведенная выше, но уже с использованием анонимного объекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int add(int a, int b) { return a + b; // анонимный объект создается для хранения и возврата результата выражения a + b } int main() { std::cout << add(4, 2); return 0; } |
При вычислении выражения a + b
, результат помещается в анонимный объект. Затем копия анонимного объекта возвращается по значению обратно в caller, и анонимный объект уничтожается.
Это работает не только с возвращаемыми значениями, но и с параметрами функции. Например, вместо этого:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> void printResult(int value) { std::cout << value; } int main() { int result = 4 + 2; printResult(result); return 0; } |
Мы можем написать это:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> void printResult(int value) { std::cout << value; } int main() { printResult(4 + 2); return 0; } |
В этом случае выражение 4 + 2
генерирует результат 6
, который помещается в анонимный объект. Затем копия этого анонимного объекта передается в функцию printResult() (которая выводит значение 6
) и уничтожается.
Обратите внимание, насколько чище стал наш код — нам не нужно засорять его временными переменными, которые используются только один раз.
Анонимные объекты класса
Хотя в вышеприведенных примерах мы использовали только фундаментальные типы данных, анонимные объекты также могут использоваться и с классами. Достаточно просто не указывать имя объекта:
1 2 |
Dollars dollars(7); // обычный объект класса Dollars(8); // анонимный объект класса |
В примере, приведенном выше, строка Dollars(8);
создаст анонимный объект класса Dollars, инициализирует его значением 8
, а затем уничтожит. В этом контексте пользы мы много не получим. Рассмотрим пример, где это может принести пользу:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream> class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } int getDollars() const { return m_dollars; } }; void print(const Dollars &dollars) { std::cout << dollars.getDollars() << " dollars."; } int main() { Dollars dollars(7); print(dollars); return 0; } |
Здесь функция main() передает объект dollars
в функцию print(). Мы можем упростить эту программу, используя анонимные объекты:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } int getDollars() const { return m_dollars; } }; void print(const Dollars &dollars) { std::cout << dollars.getDollars() << " dollars."; } int main() { print(Dollars(7)); // здесь мы передаем анонимный объект класса Dollars return 0; } |
Результат выполнения программы:
7 dollars.
Теперь рассмотрим пример сложнее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <iostream> class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } int getDollars() const { return m_dollars; } }; Dollars add(const Dollars &d1, const Dollars &d2) { Dollars sum = Dollars(d1.getDollars() + d2.getDollars()); return sum; } int main() { Dollars dollars1(7); Dollars dollars2(9); Dollars sum = add(dollars1, dollars2); std::cout << "I have " << sum.getDollars() << " dollars." << std::endl; return 0; } |
В функции add() у нас есть значение переменной sum
класса Dollars, которое используется в качестве промежуточного значения для хранения результата и его возврата. И в функции main() у нас есть значение переменной sum
класса Dollars, которое также используется, как промежуточное.
Можно сделать проще, используя анонимные объекты:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } int getDollars() const { return m_dollars; } }; Dollars add(const Dollars &d1, const Dollars &d2) { return Dollars(d1.getDollars() + d2.getDollars()); // возвращаем анонимный объект класса Dollars } int main() { Dollars dollars1(7); Dollars dollars2(9); std::cout << "I have " << add(dollars1, dollars2).getDollars() << " dollars." << std::endl; // выводим анонимный объект класса Dollars return 0; } |
Эта версия функции add() идентична вышеприведенной функции add(), за исключением того, что вместо отдельного объекта используется анонимный объект класса Dollars. Также обратите внимание, в функции main() мы больше не используем переменную с именем sum
. Вместо нее мы используем возвращаемое анонимное значение из функции add()!
В результате, наша программа стала короче, чище и проще. Фактически, поскольку dollars1
и dollars2
используются только в одном месте, мы можем еще упростить этот код с помощью анонимных объектов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> class Dollars { private: int m_dollars; public: Dollars(int dollars) { m_dollars = dollars; } int getDollars() const { return m_dollars; } }; Dollars add(const Dollars &d1, const Dollars &d2) { return Dollars(d1.getDollars() + d2.getDollars()); // возвращаем анонимный объект класса Dollars } int main() { std::cout << "I have " << add(Dollars(7), Dollars(9)).getDollars() << " dollars." << std::endl; // выводим анонимный объект класса Dollars return 0; } |
Заключение
Анонимные объекты в языке C++ используются для передачи или возврата значений без необходимости создавать большое количество временных переменных. Динамическое выделение памяти также выполняется через анонимные объекты (поэтому адрес выделенной памяти должен быть присвоен указателю, иначе мы не имели бы способа ссылаться/использовать её).
Стоит еще отметить, что анонимные объекты рассматриваются как r-values (а не как l-values, у которых есть адрес). Это означает, что анонимные объекты могут передаваться или возвращаться только по значению или по константной ссылке. В противном случае, должна использоваться переменная.
Помните, что анонимные объекты можно использовать только один раз, так как они имеют область видимости выражения. Если вам нужно ссылаться на значение в нескольких выражениях, то для этого следует использовать отдельную переменную.
Хех, так мы же их уже давно используем, может даже и с первых уроков.
Но у меня вопрос:
Я не понял как через анонимные объекты выделяется динамическая память, если они r-values ?
int a — объявление переменной с размером выделенной памяти int,
new int — запрос указателя на адрес памяти из кучи для типа int,
int *ptr = new int — присваивание адреса памяти указателю ptr,
указатель — это переменная, значением которой является адрес ячейки памяти,
переменная — это объект с именем.
У объекта с именем есть адрес, а значит переменная, или же указатель не являются r-values.
Значит в выражении int *ptr = new int нет r-values ?
У r-values нет постоянного адреса в памяти.
В new int возвращается указатель с временным адресом памяти, и, если его некуда присвоить — тут же исчезает. Временный указатель и является значением без имени, т.е анонимным объектом, т.е r-value.
Так?
Как компилятор узнает, что add — это метод именно анонимного объекта Dollars, а не какого-нибудь другого класса с таким же методом. И вроде получается, что анонимный объект не инициализирован.
Это не метод класса Dollars. Это просто функция. Среди других функций для компилятора её выделит название и принимаемые аргументы.
А поскольку она возвращает тип Dollars, можно строить конструкции типа add(Dollars(x), Dollars(y)).getDollars(), как и для любого объекта класса Dollars.
"В результате наша программа стала короче, чище и проще. "
1. Короче — да.
2. Чище — спорно:
Не уверен, что
чище чем
3. Проще — уж точно не проще это читать.
В всем должен быть баланс.
можно предложить подход в выборе "выражение или переменная ?" с опором на вложение конструкций (скобки). Если "скобок" становится слишком много (три или более в одном выражении), то лучше разбить на несколько.
Честно, не самая трудная тема. Особенно для кого-нибудь, кто дошел до 127-ой статьи(я начал с введения в ООП).
Я бы даже сказал интуитивно понятная тема. Если бы ты использовал это где нибудь в коде в другой теме и даже не закомментил, я бы понял. Но все равно класс.
хахахах, "класс" в тему)))