Работает ли RVO с элементами объекта?

Рассмотрим следующее:

struct A { /* ... */ };

A foo() {
  auto p = std::make_pair(A{}, 2);
  // ... do something
  return p.first;
}

auto a = foo();

Будет ли p.first копироваться, перемещаться или редактироваться RVO?


person Nubcase    schedule 26.10.2015    source источник
comment
Это может зависеть от реализации компилятора, но в целом я бы ожидал RVO.   -  person πάντα ῥεῖ    schedule 26.10.2015
comment
@ πάνταῥεῖ Я не думаю, что это разрешено, если выражение из оператора return является чем-то иным, кроме простого идентификатора.   -  person Piotr Skotnicki    schedule 26.10.2015
comment
@PiotrSkotnicki Хороший вопрос, кажется, я это пропустил.   -  person πάντα ῥεῖ    schedule 26.10.2015
comment
Я люблю гарантии, и насколько я знаю, вообще РВО стандартом не гарантируется, только разрешено, да? Поэтому я предпочитаю использовать, например. неконстантные ссылочные аргументы, аргументы указателя или интеллектуальные указатели, чтобы быть на 100% уверенным, что не будет выполнено ненужное копирование.   -  person Erik Alapää    schedule 26.10.2015
comment
@PiotrSkotnicki: Это неправда. Есть несколько других случаев, когда это разрешено, в первую очередь безымянные объекты.   -  person Christian Hackl    schedule 26.10.2015
comment
Если вы хотите RVO, вы можете рассмотреть std::tie, например. int n; A a; std::tie(a, n) = func(); return a;   -  person Yankes    schedule 26.10.2015
comment
@ErikAlapää делает класс подвижным, чтобы не было ненужных копий.   -  person M.M    schedule 26.10.2015
comment
@erik RVO больше, чем разрешено стандартом. Это активно поощряется.   -  person Richard Hodges    schedule 26.10.2015
comment
@M.M: Ссылки rvalue и возможность перемещения - отличное дополнение к языку, но мне все еще не на 100% удобно использовать новые конструкции, так как я не понимаю их полностью. Кроме того, в прошлый раз, когда я смотрел на перенос, все еще не было 100% гарантии, что копии никогда не будут сделаны, по крайней мере, в некоторых случаях. Итак, поскольку я старая школа и хочу точно знать, что делает мой код, я, вероятно, буду придерживаться неконстантных аргументов ссылки или указателя, чтобы получить 0 копий во всех случаях.   -  person Erik Alapää    schedule 27.10.2015
comment
Может быть, узнать об этом больше, это 100% гарантия, что класс перемещается в этом случае, если он подвижен.   -  person M.M    schedule 27.10.2015
comment
Я имею в виду в целом. Я хочу гарантий от стандарта о том, что копии не будут сделаны. Если я делаю это с помощью аргументов указателя, я знаю, что происходит, и получаю высокую производительность. Кроме того, стандартный юридический язык в целом довольно непроницаем и многословен, даже для меня, который кодировал C++ с 1992 года. Возможно, «нормативные примеры» в стандарте помогли бы работающим программистам и составителям компиляторов понять его.   -  person Erik Alapää    schedule 27.10.2015


Ответы (4)


Я обнаружил, что в Visual Studio 2010 и в gcc-5.1 RVO не применяется (см., например, http://coliru.stacked-crooked.com/a/17666dd9e532da76).

Соответствующий раздел стандарта — 12.8.31.1 [class.copy]. В нем говорится, что копирование разрешено (моя подсветка):

в операторе возврата в функции с типом возвращаемого значения класса, когда выражение является именем энергонезависимого автоматического объекта (кроме параметра функции или переменной, представленной объявлением исключения обработчик ([except.handle])) с тем же типом (игнорируя cv-квалификацию), что и тип возвращаемого значения функции, операцию копирования/перемещения можно опустить, встроив автоматический объект непосредственно в возвращаемое значение функции

Поскольку p.first не является именем объекта, RVO запрещен.

person atkins    schedule 26.10.2015
comment
p.first абсолютно не является именем объекта. :-) - person Richard Hodges; 26.10.2015
comment
Хм, значит ли это, что это работает, если вы сначала auto& r = p.first? Создает ли это новое имя в этом смысле? - person Lightness Races in Orbit; 26.10.2015
comment
@LightnessRacesinOrbit r — это имя ссылки, а не самого объекта. Так что, наверное, тоже не работает. - person rozina; 26.10.2015
comment
@rozina: На самом деле это имя для референта. В этом вся цель ссылок, в отличие от указателей. Но я не уверен, насколько это соответствует стандартной терминологии, а не английской терминологии, полученной из употребления. - person Lightness Races in Orbit; 26.10.2015
comment
@LightnessRacesinOrbit Можно быть педантичным и сказать, что он функционирует как псевдоним для объекта. У объекта может быть только одно имя, но много псевдонимов :) Было бы интересно посмотреть, как стандарт определяет его на самом деле. - person rozina; 26.10.2015
comment
@LightnessRacesinOrbit gcc-5.1 считает, что возврата ссылки даже на простой локальный объект достаточно для отключения RVO (coliru.stacked-crooked.com/a/84e5744d0a68f6dd). Интересно! - person atkins; 26.10.2015
comment
@rozina: Что такое псевдоним, если не другое имя для чего-то? - person Lightness Races in Orbit; 26.10.2015
comment
@LightnessRacesinOrbit На основе английского языка это ложная или предполагаемая личность или поддельное имя. Это другое имя. Но не оригинальное название. Я бы предположил, что имя объекта с точки зрения стандарта будет означать только исходное имя, а не псевдонимы. - person rozina; 26.10.2015
comment
@rozina: Существует множество определений подобного предполагаемого имени, альтернативного имени. - person Lightness Races in Orbit; 26.10.2015
comment
@atkins: Любопытно, что MSVC 2013 также отключает RVO в этом случае. - person Christian Hackl; 26.10.2015
comment
В 8.3.2.1 [dcl.ref] есть следующее примечание: Ссылка может рассматриваться как имя объекта. Примем ли мы это за то, что можно представить, но на самом деле это не так. ?! - person atkins; 26.10.2015
comment
@atkins: Если бы стандарт C ++ не хотел, чтобы мы думали о ссылках как об именах объектов, то он не включал бы это информативное примечание. - person Christian Hackl; 26.10.2015
comment
Это обсуждение, вероятно, может быть новым вопросом - person M.M; 26.10.2015
comment
[basic]/4 говорит, что name — это использование идентификатора [...], который обозначает объект или метку. r похоже соответствует этим критериям - person M.M; 26.10.2015
comment
@LightnessRacesinOrbit Я думаю, что имя ... предназначено для ссылки на результат поиска имени, а не на установление идентичности объекта во время выполнения (когда выражение оценивается). Учтите, что ссылка может также относиться к объекту со статической продолжительностью хранения. Выяснение того, что во время компиляции не является вычисляемым (проблема остановки) и недоступным (может зависеть от пользовательского ввода). - person Johannes Schaub - litb; 27.10.2015
comment
Стоит отметить, что p.first относится к объекту (не к переменной, но относится к объекту), а first — это имя (очевидно). И p.fist относится к объекту автоматической продолжительности хранения. Но все же оказывается, что это не имя автоматического объекта. Такие глупости случаются в черновике C++. Вероятно, потому что p.first — это не имя, что довольно тонко. Рассмотрим такую ​​функцию, как T f() { return nonStaticDataMember; }. Похоже, что это имя согласно пункту 3, но на самом деле это не так из-за неявного (*this)., добавленного перед ним пунктом 9. - person Johannes Schaub - litb; 27.10.2015
comment
@ᐅJohannesSchaub-litbᐊ Хорошо, время для нового вопроса! stackoverflow.com/q/33371684/560648 - person Lightness Races in Orbit; 27.10.2015
comment
@atkins: см. выше, пожалуйста - person Lightness Races in Orbit; 27.10.2015
comment
@ChristianHackl: см. выше, пожалуйста - person Lightness Races in Orbit; 27.10.2015
comment
@RichardHodges Никакое выражение не является именем объекта. - person curiousguy; 28.10.2015

Просто чтобы добавить немного больше топлива, как бы это функционировало, если бы RVO был в игре? Вызывающий объект поместил экземпляр A где-то в памяти, а затем вызывает foo для назначения ему (даже лучше, давайте предположим, что A был частью более крупной структуры, и давайте предположим, что он правильно выровнен, так что следующий член структура находится сразу после этого экземпляра A). Предполагая, что RVO находится в игре, часть first p расположена там, где этого хотела вызывающая сторона, но где размещается int, то есть second? Он должен идти сразу после экземпляра A, чтобы поддерживать правильное функционирование pair, но в исходном местоположении есть какой-то другой член сразу после этого экземпляра A.

Я ожидаю, что RVO не будет происходить в этом месте, поскольку вы возвращаете только часть более крупного объекта. Перемещение может произойти, так как first придется оставить в разрушаемом состоянии.

person Andre Kostur    schedule 26.10.2015

@atkins пришел сюда первым с ответом. Просто добавьте эту небольшую тестовую программу, которая может оказаться полезной в будущем при отслеживании поведения перемещения/назначения.

#include <iostream>
#include <string>

using namespace std::string_literals;

struct A {
    A()
    : history("created")
    {
    }

    A(A&& r)
    : history("move-constructed,"s + r.history)
    {
        r.history = "zombie: was "s + r.history;
    }
    A(const A& r)
    : history("copied from: " + r.history)
    {
    }
    ~A() {
        history = "destroyed,"s + history;
        std::cout << history << std::endl;
    }
    A& operator=(A&& r) {
        history = "move-assigned from " + r.history + " (was "s + history + ")"s;
        r.history = "zombie: was "s + r.history;
        return *this;
    }
    A& operator=(const A&r ) {
        history = "copied from " + r.history;
        return *this;
    }
    std::string history;
};

A foo() {
    auto p = std::make_pair(A{}, 2);
    // ... do something
    return p.first;
}



auto main() -> int
{
    auto a = foo();
    return 0;
}

пример вывода:

destroyed,zombie: was created
destroyed,move-constructed,created
destroyed,copied from: move-constructed,created
person Richard Hodges    schedule 26.10.2015

Рассмотрим следующий код:

struct A {};
struct B {};
struct C { B c[100000]; };

A callee()
{
    struct S
    {
        A a;
        C c;
    } s;
    return s.a;
}

void caller()
{
    A a = callee();
    // here should lie free unused spacer of size B[100000]
    B b;
}

«Частичное» RVO должно привести к чрезмерному увеличению использования стека в вызывающем объекте, потому что (я думаю) S может быть построено только полностью во фрейме стека вызывающего объекта.

Другая проблема связана с поведением ~S():

// a.hpp
struct A {};
struct B {};
struct C { A a; B b; ~C(); };
// a.cpp
#include "a.hpp"
~C() { /* ... */; }
// main.cpp
#include "a.hpp"
A callee()
{
    C c;
    return c.a;
} // How to destruct c partially, having the user defined ~C() in another TU?
// Even if destructor is inline and its body is visible,
// how to automatically change its logic properly?
// It is impossible in general case.
void caller() { A a = callee(); }
person Tomilov Anatoliy    schedule 26.10.2015
comment
1) должно привести к чрезмерному раздуванию стека в вызывающей программе. Плохой аргумент: оптимизация не будет применена, если она вызывает абсурдные накладные расходы памяти; -1 для этого. 2) Как частично уничтожить c Частичное уничтожение не существует. +1 за этот аргумент. - person curiousguy; 28.10.2015
comment
@curiousguy Я пересмотрел первый аргумент, но ответ остается частично недействительным для истории :). - person Tomilov Anatoliy; 28.10.2015