TGViewer
this->notes. this->notes. @thisnotes · 4.52K subscribers
Post #198 1.76K
#cpp

RVO/NRVO 1/2.

Давно хотел рассказать про всякие оптимизации, которые делаются в плюсах. Тут будет пост про RVO/NRVO, а позже про что-нибудь ещё.
Может в начале будут какие-то неявные допущения или грубые формулировки, но к концу поста постараюсь донести все детали, чтобы картина сложилась.

Для начала про терминологию в стандарте. Формально есть общий термин copy elision, который в целом о том, чтобы не делать лишних копий объекта и создавать его сразу там, где нужно. Например ниже примеры copy elision:

T t = T(); // not T() + operator= but only T();
T t = T(T(T(T(T(T(T(1))))))); // not T(1) + many T(const T&) + operator= but only T(1)


Вот что говорит стандарт ([class.copy.elision]):

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects.

Т.е. компилятор в праве применять эту оптимизацию в обход copy-/move-кторов, даже если в них или деструкторе есть какие-то сайдэффекты.

Так же тут есть 4 пункта. В первом говорится, что тип инициализирующего выражения и тип переменной с итоговым результатом должны совпадать. Это главное условие для RVO/NRVO.

RVO/NRVO это частные случаи copy elision, которые происходят в результате возврата чего-либо из функции. Далее мы будем пользовать примерно таким классом, чтобы понять, что происходит с объектами:

#define TELL(str) { std::cout << str << std::endl; }
struct S {
S() TELL("def")
S(const S&) TELL("cp")
S(S&&) TELL("mv")
};


Так же есть такой термин как temporary materialization – принцип, согласно которому prvalue-выражение не создаётся физически в памяти до тех пор, пока оно не будет присвоено в не prvalue-объект.

Return Value Optimization по сути и есть пример temporary materialization. Посмотрим на базовый пример (godbolt):

S f() { return S(); }
S s = f();


Тут у нас prvalue-выражение S(), благодаря чему мы можем не создавать переменную в момент его появления в return и присвоить создать лишь в s. И из всех интересных нам конструкторов будет вызван лишь конструктор по умолчанию. cv-квалификаторы в сигнатуре функции в данном случае нам ничего не портят (gb).

А если мы можем (не)явно сконструировать возвращаемый тип из выражения в return? Добавим в S такой конструктор:

S(std::string&&) TELL("str&&")

и собственно пример (gb):

S f() { return std::string(10, '1'); }
S s = f();


Всё хорошо, даже не смотря на то, что тип в return и в сигнатуре не совпадают. По факту компилятор представляет выражение в return как S(std::string(10, ‘1’)), что тоже является prvalue. Даже в таком случае всё в порядке (gb):

S f() {
std::string s(10, '1');
return s;
}


Всё корректно. Т.к. компилятор справляется тут даже сделать неявный move переменной s. Причём это всё ещё RVO.

Стоит понимать, что с C++17 RVO — гарантированная штука и даже не оптимизация.
Давайте поймём, как оно работает. У компилятора есть адрес места в памяти, где должен оказаться результат выполнения функции (называется return slot). Когда выполняются условия для RVO, компилятор может сразу создать объект из вашего return в нужном месте в памяти. Т.е. это скорее про свойства инициализации объектов.

Named Return Value Optimization уже оптимизация (причём не гарантированная, тут компиляторы делают кто что может). Базовый пример (gb):

S f() { S s; return s; }

Или пример посложнее, где в S появился int x (gb):

S f() { S s; s.x = 1; return s; }

void gg(S& s) { s.x = 1; }
S g() {
S s; gg(s); return s;
}


Компилятор всё ещё может создавать локальную переменную s в функциях сразу в return slot итогового объекта.
Для NRVO правда volatile для локальной переменной всё портит (gb).

Почему важно знать об этих штуках? Потому что NRVO можно сломать (gb):

S f() {
S s;
return std::move(s); // return S&&
}


Теперь компилятора обязан создать объект и мувнуть его, потому что это то, о чём вы его попросили явно. Так же можно думать, что у выражения в return и в сигнатуре теперь разные типы (S&& и S).
  • ❤ 7
  • 👍 4
More from @thisnotes
  1. Sep 17, 2026#common Сидите вы себе спокойно, разрабатываете поиск каких-нибудь объектов. Может это тов…
  2. Sep 9, 2026#cpp #books Да, книга 2001ого года. Мы ровесники. И да, в ней в основном обсуждаются какие…
  3. Sep 2, 2026#perf Попробовал собрать в кучку (кажется, немного сумбурно всё же) мысли по двум моментам…
  4. Aug 31, 2026Давайте новый тег заведём: #perf Во-первых, надо понять, что я вообще понимаю под перфом,…
  5. Aug 27, 2026#common Мы часто делаем системы, которые обладают какими-то ограничениями. Ограничения наш…
  6. Aug 24, 2026#list 0. [talk] Achieving Peak Performance for Matrix Multiplication in C++. Aliaksei Sala…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →