const& foo VS const foo
От: valexey_  
Дата: 19.03.13 14:39
Оценка:
Вопрос — отличается ли семантика ссылки на константу от самой константы в следующем случае:

Foo foo();
...
const Foo& a = foo();
const Foo  b = foo();


Соответственно тип Foo — произвольный.
c++
Re: const& foo VS const foo
От: saf_e  
Дата: 19.03.13 15:34
Оценка: -7
Здравствуйте, valexey_, Вы писали:

_>Вопрос — отличается ли семантика ссылки на константу от самой константы в следующем случае:


_>
_>Foo foo();
_>...
_>const Foo& a = foo();
_>const Foo  b = foo();
_>


_>Соответственно тип Foo — произвольный.


Foo foo();
...
const Foo& a = foo(); // здесь будет ссылка на временный объект, который будет разрушен и следовательно ссылка будет невалидна
const Foo  b = foo(); // здесь получите полноценную копию
Re[2]: const& foo VS const foo
От: Evgeny.Panasyuk Россия  
Дата: 19.03.13 15:41
Оценка: 1 (1)
Здравствуйте, saf_e, Вы писали:

_>const Foo& a = foo(); // здесь будет ссылка на временный объект, который будет разрушен и следовательно ссылка будет невалидна


http://www.lmgtfy.com/?q=C%2B%2B+const+reference+prolongs+life+of&l=1
Re[2]: const& foo VS const foo
От: valexey_  
Дата: 19.03.13 15:44
Оценка:
Здравствуйте, saf_e, Вы писали:

_>Здравствуйте, valexey_, Вы писали:


_>>Вопрос — отличается ли семантика ссылки на константу от самой константы в следующем случае:


_>>
_>>Foo foo();
_>>...
_>>const Foo& a = foo();
_>>const Foo  b = foo();
_>>


_>>Соответственно тип Foo — произвольный.


_>
_>Foo foo();
_>...
_>const Foo& a = foo(); // здесь будет ссылка на временный объект, который будет разрушен и следовательно ссылка будет невалидна
_>const Foo  b = foo(); // здесь получите полноценную копию
_>


IMHO ты путаешь с указателем на временный объект. Например согласно 14.1/6 стандарта (точнее драфта стандарта от 2005 года: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1905.pdf страница 277) имеем такое:
const int & cri = i ; // OK: const reference bound to temporary


const reference на возвращаемое значение — абсолютно валидная операция (подробней см. 12.2)

Ссылка (ака reference) не сводится к константному указателю в С++.
Re[3]: const& foo VS const foo
От: saf_e  
Дата: 19.03.13 15:46
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Здравствуйте, saf_e, Вы писали:


_>>const Foo& a = foo(); // здесь будет ссылка на временный объект, который будет разрушен и следовательно ссылка будет невалидна


EP>http://www.lmgtfy.com/?q=C%2B%2B+const+reference+prolongs+life+of&l=1


Спасибо почитал
Re: const& foo VS const foo
От: uzhas Ниоткуда  
Дата: 19.03.13 15:49
Оценка:
Здравствуйте, valexey_, Вы писали:

_>const Foo& a = foo();

_>const Foo b = foo();

лично я приучил себя использовать первый вариант в надежде, что компилятор соптимизирует что-то там и разместить объект где-нибудь в легко доступном месте (хотя куда уж шустрее стека?)
но на практике так и не ощутил выигрыша
видимо, не умею готовить
Re[2]: const& foo VS const foo
От: valexey_  
Дата: 19.03.13 15:51
Оценка:
Здравствуйте, uzhas, Вы писали:

U>Здравствуйте, valexey_, Вы писали:


_>>const Foo& a = foo();

_>>const Foo b = foo();

U>лично я приучил себя использовать первый вариант в надежде, что компилятор соптимизирует что-то там и разместить объект где-нибудь в легко доступном месте (хотя куда уж шустрее стека?)

U>но на практике так и не ощутил выигрыша
U>видимо, не умею готовить

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

Быть может есть различие в последующей жизни a и b? Может есть что-то такое что можно с 'a' сделать но нельзя с 'b' и наоборот?
Re[2]: const& foo VS const foo
От: saf_e  
Дата: 19.03.13 15:51
Оценка:
Здравствуйте, uzhas, Вы писали:

U>Здравствуйте, valexey_, Вы писали:


_>>const Foo& a = foo();

_>>const Foo b = foo();

U>лично я приучил себя использовать первый вариант в надежде, что компилятор соптимизирует что-то там и разместить объект где-нибудь в легко доступном месте (хотя куда уж шустрее стека?)

U>но на практике так и не ощутил выигрыша
U>видимо, не умею готовить

Как-то это прошло мимо меня Всю жизнь избегал такого, опасаясь что объект будет разрушен и надеялся на return-value optimization
Re[3]: const& foo VS const foo
От: saf_e  
Дата: 19.03.13 15:53
Оценка:
Здравствуйте, valexey_, Вы писали:

_>Здравствуйте, uzhas, Вы писали:


U>>Здравствуйте, valexey_, Вы писали:


_>>>const Foo& a = foo();

_>>>const Foo b = foo();

U>>лично я приучил себя использовать первый вариант в надежде, что компилятор соптимизирует что-то там и разместить объект где-нибудь в легко доступном месте (хотя куда уж шустрее стека?)

U>>но на практике так и не ощутил выигрыша
U>>видимо, не умею готовить

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


_>Быть может есть различие в последующей жизни a и b? Может есть что-то такое что можно с 'a' сделать но нельзя с 'b' и наоборот?


Тогда получается, что если компилятор не умеет делать return-value optimization, то первый вариант эффективнее. Больше никакой разницы не наблюдаю...
Re[4]: const& foo VS const foo
От: valexey_  
Дата: 19.03.13 16:27
Оценка:
Здравствуйте, saf_e, Вы писали:

_>Тогда получается, что если компилятор не умеет делать return-value optimization, то первый вариант эффективнее. Больше никакой разницы не наблюдаю...


Не думаю что все так просто. Быть может семантика const_cast'а над этими значениями будет отличаться например.
Re[5]: const& foo VS const foo
От: saf_e  
Дата: 19.03.13 16:35
Оценка:
Здравствуйте, valexey_, Вы писали:

_>Здравствуйте, saf_e, Вы писали:


_>>Тогда получается, что если компилятор не умеет делать return-value optimization, то первый вариант эффективнее. Больше никакой разницы не наблюдаю...


_>Не думаю что все так просто. Быть может семантика const_cast'а над этими значениями будет отличаться например.


Не думаю что тут есть какие-то сложности. Семантически ссылка на объект и сам объект отличаются только временем жизни. А в данном примере не отличаются и этим
Re[6]: const& foo VS const foo
От: valexey_  
Дата: 19.03.13 16:40
Оценка: -1
Здравствуйте, saf_e, Вы писали:

_>>Не думаю что все так просто. Быть может семантика const_cast'а над этими значениями будет отличаться например.


_>Не думаю что тут есть какие-то сложности. Семантически ссылка на объект и сам объект отличаются только временем жизни. А в данном примере не отличаются и этим


Ну вот чуть другой пример, где поведение отличается на некоторых компиляторах:

#include <iostream>
using namespace std;

void foo(int* p) {*p=12;}

int main() {
    const int& a = 42;
    const int  b = 42;
    foo(const_cast<int*>(&a));
    foo(const_cast<int*>(&b));
    cout << a << " " << b  << endl;
    return 0;
}
Re[7]: const& foo VS const foo
От: saf_e  
Дата: 19.03.13 16:44
Оценка: +1
Здравствуйте, valexey_, Вы писали:

_>Здравствуйте, saf_e, Вы писали:


_>>>Не думаю что все так просто. Быть может семантика const_cast'а над этими значениями будет отличаться например.


_>>Не думаю что тут есть какие-то сложности. Семантически ссылка на объект и сам объект отличаются только временем жизни. А в данном примере не отличаются и этим


_>Ну вот чуть другой пример, где поведение отличается на некоторых компиляторах:


_>
_>#include <iostream>
_>using namespace std;

_>void foo(int* p) {*p=12;}

_>int main() {
_>    const int& a = 42;
_>    const int  b = 42;
_>    foo(const_cast<int*>(&a));
_>    foo(const_cast<int*>(&b));
_>    cout << a << " " << b  << endl;
_>    return 0;
_>}
_>


Выполнение этого кода undefined behaviuor в чистом виде.
Re[8]: const& foo VS const foo
От: valexey_  
Дата: 19.03.13 16:47
Оценка:
Здравствуйте, saf_e, Вы писали:

_>>Ну вот чуть другой пример, где поведение отличается на некоторых компиляторах:


_>>
_>>#include <iostream>
_>>using namespace std;

_>>void foo(int* p) {*p=12;}

_>>int main() {
_>>    const int& a = 42;
_>>    const int  b = 42;
_>>    foo(const_cast<int*>(&a));
_>>    foo(const_cast<int*>(&b));
_>>    cout << a << " " << b  << endl;
_>>    return 0;
_>>}
_>>


_>Выполнение этого кода undefined behaviuor в чистом виде.


Для обоих случаев? И если да, то согласно чему это будет UB?
Re[9]: const& foo VS const foo
От: saf_e  
Дата: 19.03.13 16:54
Оценка:
Здравствуйте, valexey_, Вы писали:

_>Здравствуйте, saf_e, Вы писали:


_>>>Ну вот чуть другой пример, где поведение отличается на некоторых компиляторах:


_>>>
_>>>#include <iostream>
_>>>using namespace std;

_>>>void foo(int* p) {*p=12;}

_>>>int main() {
_>>>    const int& a = 42;
_>>>    const int  b = 42;
_>>>    foo(const_cast<int*>(&a));
_>>>    foo(const_cast<int*>(&b));
_>>>    cout << a << " " << b  << endl;
_>>>    return 0;
_>>>}
_>>>


_>>Выполнение этого кода undefined behaviuor в чистом виде.


_>Для обоих случаев? И если да, то согласно чему это будет UB?


У вас нет гарантии что память в которой размещены константы будет доступна на запись, это раз.
И второе, компилятор с чистой совестью может полагать что значение констант, в данном коде, не будет изменено.
Re[10]: const& foo VS const foo
От: valexey_  
Дата: 19.03.13 17:05
Оценка:
Здравствуйте, saf_e, Вы писали:

_>>>>
_>>>>#include <iostream>
_>>>>using namespace std;

_>>>>void foo(int* p) {*p=12;}

_>>>>int main() {
_>>>>    const int& a = 42;
_>>>>    const int  b = 42;
_>>>>    foo(const_cast<int*>(&a));
_>>>>    foo(const_cast<int*>(&b));
_>>>>    cout << a << " " << b  << endl;
_>>>>    return 0;
_>>>>}
_>>>>


_>>>Выполнение этого кода undefined behaviuor в чистом виде.


_>>Для обоих случаев? И если да, то согласно чему это будет UB?


_>У вас нет гарантии что память в которой размещены константы будет доступна на запись, это раз.

_>И второе, компилятор с чистой совестью может полагать что значение констант, в данном коде, не будет изменено.

В случае const int& -- IMHO, есть. По аналогии с тем же 14.1/6, будет гарантировано const reference bound to temporary. Что в общем то и наблюдаем в случае gcc: 'a' в результате манипуляций меняется на 12, а вот 'b' нет.

Для пущей ясности, уберем литерал и заменим его на "переменную":

#include <iostream>
using namespace std;

void foo(int* p) {*p=12;}

int main() {
    const int z = 42;
    const int& a = z;
    const int  b = z;
    foo(const_cast<int*>(&a));
    foo(const_cast<int*>(&b));
    cout << a << " " << b << " " << z  << endl;
    return 0;
}


Результат:
12 42 42

Таким образом z и b не изменились, а вот содержимое 'a' изменилось.

Результат одинаков на clang и gcc. Пробовал и с -O0 и c -O3
Re[11]: const& foo VS const foo
От: valexey_  
Дата: 19.03.13 17:09
Оценка:
_>Результат одинаков на clang и gcc. Пробовал и с -O0 и c -O3

Впрочем, я не утверждаю что тут нет UB. Просто хочется разобраться. И очевидно отличия в семантике какие-то все же есть, коль вылезает разное поведение (пусть даже из за UB) на некоторых компиляторах.
Re[12]: const& foo VS const foo
От: saf_e  
Дата: 19.03.13 17:15
Оценка:
Здравствуйте, valexey_, Вы писали:

_>>Результат одинаков на clang и gcc. Пробовал и с -O0 и c -O3


_>Впрочем, я не утверждаю что тут нет UB. Просто хочется разобраться. И очевидно отличия в семантике какие-то все же есть, коль вылезает разное поведение (пусть даже из за UB) на некоторых компиляторах.


Тут скорее вопрос как конкретный компилятор трактует код.

Могу сказать одно, практического смысла тут 0, такого кода стоит избегать. ИМХО, в любых практических аспектах разницы быть не должно.
Re[11]: const& foo VS const foo
От: watch-maker  
Дата: 19.03.13 17:19
Оценка:
Здравствуйте, valexey_, Вы писали:

_>>>согласно чему это будет UB?

7.1.5.1 4
Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.

Дальше в стандарте идёт пример, где показано когда запись по указателю, полученному через const_cast, делать можно, а когда нельзя. Похожий на твой код там есть, и отнюдь не в разделе «можно».

_>>У вас нет гарантии что память в которой размещены константы будет доступна на запись, это раз.

Это ещё не самое страшное. Ужасы начинаются когда когда значение константы таки удаётся изменить. И тогда где-то в другом месте программы вмести «42» будет подставляться «12».

_>В случае const int& -- IMHO, есть. По аналогии с тем же 14.1/6, будет гарантировано const reference bound to temporary. Что в общем то и наблюдаем в случае gcc: 'a' в результате манипуляций меняется на 12, а вот 'b' нет.


_>Для пущей ясности, уберем литерал и заменим его на "переменную":

_>Результат одинаков на clang и gcc. Пробовал и с -O0 и c -O3

Это всё UB, причём довольно неинтересное.
Re[12]: const& foo VS const foo
От: saf_e  
Дата: 19.03.13 17:21
Оценка:
Здравствуйте, watch-maker, Вы писали:

WM>Здравствуйте, valexey_, Вы писали:


_>>>>согласно чему это будет UB?

WM>

7.1.5.1 4
WM>Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.

WM>Дальше в стандарте идёт пример, где показано когда запись по указателю, полученному через const_cast, делать можно, а когда нельзя. Похожий на твой код там есть, и отнюдь не в разделе «можно».

_>>>У вас нет гарантии что память в которой размещены константы будет доступна на запись, это раз.

WM>Это ещё не самое страшное. Ужасы начинаются когда когда значение константы таки удаётся изменить. И тогда где-то в другом месте программы вмести «42» будет подставляться «12».

_>>В случае const int& -- IMHO, есть. По аналогии с тем же 14.1/6, будет гарантировано const reference bound to temporary. Что в общем то и наблюдаем в случае gcc: 'a' в результате манипуляций меняется на 12, а вот 'b' нет.


_>>Для пущей ясности, уберем литерал и заменим его на "переменную":

_>>Результат одинаков на clang и gcc. Пробовал и с -O0 и c -O3

WM>Это всё UB, причём довольно неинтересное.


Когда UB интересное его начинают активно использовать и это еще хуже
Re[12]: const& foo VS const foo
От: valexey_  
Дата: 19.03.13 17:26
Оценка:
Здравствуйте, watch-maker, Вы писали:

WM>Здравствуйте, valexey_, Вы писали:


_>>>>согласно чему это будет UB?

WM>

7.1.5.1 4
WM>Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.

WM>Дальше в стандарте идёт пример, где показано когда запись по указателю, полученному через const_cast, делать можно, а когда нельзя. Похожий на твой код там есть, и отнюдь не в разделе «можно».

Спасибо. Теперь понятно.

Тогда возвращаемся в изначальному вопросу — есть ли и обнаружима ли разница между a и b в этом примере:
Foo foo();
...
const Foo& a = foo();
const Foo  b = foo();


Естественно затем с 'a' и 'b' можно проводить любые эксперименты (избегая UB естественно) чтобы обнаружить различие в поведении.
Re[13]: const& foo VS const foo
От: watch-maker  
Дата: 19.03.13 17:42
Оценка:
Здравствуйте, saf_e, Вы писали:

WM>>Это всё UB, причём довольно неинтересное.


_>Когда UB интересное его начинают активно использовать и это еще хуже :)


Все случаи UB в C/C++ — это плата за производительность. Если следить за тем, когда возникает UB, то использовать UB вполне можно и не нарушая правильности программы.
Так в некоторых компиляторах (да в тех же gcc, clang, msvc, icc) описана специальная функция, попытка вызова которой из программы гарантированно вызывает UB. Разумеется, если управление дойдёт до неё, то ничего хорошего не будет. Но если пометить с её помощью невозможные программные пути, то это откроет компилятору путь к генерации более быстрого кода. То есть при таком использовании программа может, во-первых, остаться верной, а, во-вторых, стать более быстрой.
Re[3]: const& foo VS const foo
От: Кодт Россия  
Дата: 19.03.13 18:37
Оценка: 6 (1) +1
Здравствуйте, saf_e, Вы писали:

_>Как-то это прошло мимо меня Всю жизнь избегал такого, опасаясь что объект будет разрушен и надеялся на return-value optimization


RVO здесь не при чём, потому что оно относится к внутренностям функции, которая это значение возвращает.
Тут другая, похожая оптимизация: выкидывание промежуточного конструктора копирования при инициализации объекта.
Foo f = Foo(1,2,3); // формально, cctor: Foo f(Foo(1,2,3)), а фактически, это эквивалентно
Foo f(1,2,3);

Естественно, что объект должен быть copy-constructible (вторя строка этого не требует).

В случае с функцией картина похожая, но это зависит от того, как реализуется возвращение значения: через скрытый out-параметр или честно, через регистры или повторно используемые буферы.

Out-параметр
Foo foo(.....)
{
  // надежда на RVO
  return Foo(1,2,3);

  // надежда на NRVO
  Foo f(1,2,3);
  ..... f .....
  return f;
}
void bar()
{
  foo(.....);
  Foo const& r = foo(.....);
  Foo v = foo(.....);
}

void foo(Foo* dst, .....)
{
  // RVO
  new(dst) Foo(1,2,3);
  return;

  // NRVO
  Foo& f = *new(dst) Foo(1,2,3);
  ..... f .....
  return;
}
void bar()
{
  {uninitialized<Foo> t0;
  foo(&t0, .....);
  }// и тут же вызвали деструктор

  uninitialized<Foo> t1;
  foo(&t1, .....);
  Foo const& r = t1; // тут - тривиальнее некуда; память под ссылку, естественно, не размещается

  uninitialized<Foo> t2;
  foo(&t2, .....);
  Foo v = t2; // вызвали cctor
  // либо
  uninitialized<Foo> v;
  foo(&v, .....); // размещаем прямо в целевой переменной
}

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

Отсюда мораль: ссылка лучше.

Тут есть ещё одна тонкость, кстати говоря.
Формально, любая переменная имеет адрес. Но компилятор, если видит, что адрес нигде не используется, может размещать её "в уме" — в регистрах, в аппаратном стеке FPU.
То есть, если объект вернули по значению через регистр, то он так и может болтаться в регистрах, никуда не копируясь или копируясь в незадействованные, чтобы освободить основной аккумулятор.
Вот тут становится интересно: раз мы взяли ссылку, является ли это для компилятора поводом "приземлить" переменную, или он продолжает думать о ссылке, как всего лишь о псевдониме?

Отсюда мораль: переменная естественнее.



Ну и ещё 3 момента, все о них знают, но лучше упомянуть лишний раз. Если функция возвращает не Foo...
// наследник
Derived foo_d();
Foo        dv = foo_d(); // срезка
Foo const& dr = foo_d(); // срезки нет, получили ссылку на подобъект-базу

// ссылка
Foo const& foo_r();
Foo        rv = foo_r(); // скопировали
Foo const& rr = foo_r(); // запомнили адрес, без продления

// произвольный тип
Compatible foo_c();
Foo        cv = foo_c(); // приведение типа
Foo const& cr = foo_c(); // приведение типа, и не дай бог там есть Compatible::operator Foo const&()

Отсюда мораль: смотря чего больше боишься — срезки или непродлённого времени.
Перекуём баги на фичи!
Re[4]: const& foo VS const foo
От: Evgeny.Panasyuk Россия  
Дата: 19.03.13 19:04
Оценка: 36 (2)
Здравствуйте, Кодт, Вы писали:

К>RVO здесь не при чём, потому что оно относится к внутренностям функции, которая это значение возвращает.

К>Тут другая, похожая оптимизация: выкидывание промежуточного конструктора копирования при инициализации объекта.
К>
К>Foo f = Foo(1,2,3); // формально, cctor: Foo f(Foo(1,2,3)), а фактически, это эквивалентно
К>Foo f(1,2,3);
К>

К>Естественно, что объект должен быть copy-constructible (вторя строка этого не требует).

Кстати:
struct Foo
{
   Foo(const Foo&)=delete;
   Foo(Foo &&)=delete;
   Foo &operator=(const Foo&)=delete;
   Foo &operator=(Foo &&)=delete;
   
   Foo(int){}
};

Foo create()
{
   //return Foo{0}; // ERROR: needs Foo(Foo &&)
   return {0};
}

int main()
{
   const Foo &t1=create(); // OK
   (void)t1;

   Foo t2=create(); // ERROR:
   //use of deleted function Foo(Foo::Foo&&)
}
Re[5]: const& foo VS const foo
От: valexey_  
Дата: 19.03.13 19:12
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Кстати:

EP>
EP>struct Foo
EP>{
EP>   Foo(const Foo&)=delete;
EP>   Foo(Foo &&)=delete;
EP>   Foo &operator=(const Foo&)=delete;
EP>   Foo &operator=(Foo &&)=delete;
   
EP>   Foo(int){}
EP>};

EP>Foo create()
EP>{
EP>   //return Foo{0}; // ERROR: needs Foo(Foo &&)
EP>   return {0};
EP>}

EP>int main()
EP>{
EP>   const Foo &t1=create(); // OK
EP>   (void)t1;

EP>   Foo t2=create(); // ERROR:
EP>   //use of deleted function Foo(Foo::Foo&&)
EP>}
EP>



А если
const Foo t3 = create(); // ?
Re[11]: const& foo VS const foo
От: _niko_ Россия  
Дата: 19.03.13 19:20
Оценка:
Здравствуйте, valexey_, Вы писали:

_>
_>#include <iostream>
_>using namespace std;

_>void foo(int* p) {*p=12;}

_>int main() {
_>    const int z = 42;
_>    const int& a = z;
_>    const int  b = z;
_>    foo(const_cast<int*>(&a));
_>    foo(const_cast<int*>(&b));
_>    cout << a << " " << b << " " << z  << endl;
_>    return 0;
_>}
_>


_>Результат:

_>
_>12 42 42
_>

_>Таким образом z и b не изменились, а вот содержимое 'a' изменилось.
_>Результат одинаков на clang и gcc. Пробовал и с -O0 и c -O3

Благодаря const компилятор "считает" что значение переменных не низменно, посему не "считывает их реальные значения" при каждом запросе.
Но это не значит что это так, как только мы добавляем в объявление переменных ключевое слово volatile,
так сразу видим что на самом то деле функция foo изменяет их.
Re[6]: const& foo VS const foo
От: Evgeny.Panasyuk Россия  
Дата: 19.03.13 19:32
Оценка: 1 (1)
Здравствуйте, valexey_, Вы писали:

_>А если

_>
_>const Foo t3 = create(); // ?
_>


Естественно тоже самое, просто забыл добавить.
Re[4]: const& foo VS const foo
От: Erop Россия  
Дата: 19.03.13 19:45
Оценка:
Здравствуйте, Кодт, Вы писали:


К>Тут есть ещё одна тонкость, кстати говоря.

К>Формально, любая переменная имеет адрес. Но компилятор, если видит, что адрес нигде не используется, может размещать её "в уме" — в регистрах, в аппаратном стеке FPU.
К>То есть, если объект вернули по значению через регистр, то он так и может болтаться в регистрах, никуда не копируясь или копируясь в незадействованные, чтобы освободить основной аккумулятор.
К>Вот тут становится интересно: раз мы взяли ссылку, является ли это для компилятора поводом "приземлить" переменную, или он продолжает думать о ссылке, как всего лишь о псевдониме?

Формально же ссылка не является адресом или указателем и в хранилище не нуждается.
Автоматическая ссылка на временную переменную -- это просто имя этой переменной и всё...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[13]: const& foo VS const foo
От: rusted Беларусь  
Дата: 19.03.13 19:53
Оценка:
Здравствуйте, valexey_, Вы писали:

_>Тогда возвращаемся в изначальному вопросу — есть ли и обнаружима ли разница между a и b в этом примере:

_>
_>Foo foo();
_>...
_>const Foo& a = foo();
_>const Foo  b = foo();
_>


_>Естественно затем с 'a' и 'b' можно проводить любые эксперименты (избегая UB естественно) чтобы обнаружить различие в поведении.


Не про разницу в поведении, а просто про преимущества варианта со ссылкой. Если в процессе развития кода foo() поменяется и начнет возвращать константную ссылку, то в первом варианте у нас и так и так будет всё оптимально, а во втором появится ненужное копирование.

В качестве примера: boost::filesystem::path::generic_string() — под win возвращает новую строку, под nix — ссылку на внутреннюю. И если мы хотим избежать лишних копий и в тоже время иметь один и тот же код на разных платформах, то присваивание ссылке тут в самый раз.
Re[4]: const& foo VS const foo
От: saf_e  
Дата: 20.03.13 08:30
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, saf_e, Вы писали:


_>>Как-то это прошло мимо меня Всю жизнь избегал такого, опасаясь что объект будет разрушен и надеялся на return-value optimization


К>RVO здесь не при чём, потому что оно относится к внутренностям функции, которая это значение возвращает.

К>Тут другая, похожая оптимизация: выкидывание промежуточного конструктора копирования при инициализации объекта.
К>
К>Foo f = Foo(1,2,3); // формально, cctor: Foo f(Foo(1,2,3)), а фактически, это эквивалентно
К>Foo f(1,2,3);
К>


Может быть я еще чего-то не знаю, поправьте если так, но мне казалось что RVO это как раз и есть оптимизация по выкидыванию лишнего копирования...
И код
К>
К>Foo f = Foo(1,2,3); // формально, cctor: Foo f(Foo(1,2,3)), а фактически, это эквивалентно
К>

ничем не отличается, по идеологии, от:
Foo f = Foo::create(1,2,3);
Re[7]: const& foo VS const foo
От: valexey_  
Дата: 20.03.13 15:45
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Здравствуйте, valexey_, Вы писали:


_>>А если

_>>
_>>const Foo t3 = create(); // ?
_>>


EP>Естественно тоже самое, просто забыл добавить.

Спасибо. Совсем забыл про сематику перемещения в новом стандарте.
Re[14]: const& foo VS const foo
От: valexey_  
Дата: 20.03.13 15:47
Оценка:
Здравствуйте, rusted, Вы писали:

_>>Естественно затем с 'a' и 'b' можно проводить любые эксперименты (избегая UB естественно) чтобы обнаружить различие в поведении.


R>Не про разницу в поведении, а просто про преимущества варианта со ссылкой. Если в процессе развития кода foo() поменяется и начнет возвращать константную ссылку, то в первом варианте у нас и так и так будет всё оптимально, а во втором появится ненужное копирование.


R>В качестве примера: boost::filesystem::path::generic_string() — под win возвращает новую строку, под nix — ссылку на внутреннюю. И если мы хотим избежать лишних копий и в тоже время иметь один и тот же код на разных платформах, то присваивание ссылке тут в самый раз.


Ну, это в общем то тоже разная семантика при изменении "внешнего окружения". Так что вполне подходит под изначальную задачу. Спасибо.
Re[8]: const& foo VS const foo
От: Evgeny.Panasyuk Россия  
Дата: 20.03.13 17:39
Оценка:
Здравствуйте, valexey_, Вы писали:

_>>>А если

_>>>
_>>>const Foo t3 = create(); // ?
_>>>


EP>>Естественно тоже самое, просто забыл добавить.

_>Спасибо. Совсем забыл про сематику перемещения в новом стандарте.

в этом примере как раз нет ни перемещения, ни копирования, которые нужны для:
const Foo t3 = create();
Re[14]: const& foo VS const foo
От: sts  
Дата: 20.03.13 19:14
Оценка:
Здравствуйте, watch-maker, Вы писали:

WM>Здравствуйте, saf_e, Вы писали:


WM>>>Это всё UB, причём довольно неинтересное.


_>>Когда UB интересное его начинают активно использовать и это еще хуже


WM>Все случаи UB в C/C++ — это плата за производительность. Если следить за тем, когда возникает UB, то использовать UB вполне можно и не нарушая правильности программы.

WM>Так в некоторых компиляторах (да в тех же gcc, clang, msvc, icc) описана специальная функция, попытка вызова которой из программы гарантированно вызывает UB. Разумеется, если управление дойдёт до неё, то ничего хорошего не будет. Но если пометить с её помощью невозможные программные пути, то это откроет компилятору путь к генерации более быстрого кода. То есть при таком использовании программа может, во-первых, остаться верной, а, во-вторых, стать более быстрой.

Напиши пожалуйста про эту функцию поподробнее, хотя бы с одним примером использования?
Re[15]: __builtin_unreachable
От: watch-maker  
Дата: 20.03.13 22:55
Оценка: 65 (5)
Здравствуйте, sts, Вы писали:

WM>>Все случаи UB в C/C++ — это плата за производительность. Если следить за тем, когда возникает UB, то использовать UB вполне можно и не нарушая правильности программы.

WM>>Так в некоторых компиляторах (да в тех же gcc, clang, msvc, icc) описана специальная функция, попытка вызова которой из программы гарантированно вызывает UB. Разумеется, если управление дойдёт до неё, то ничего хорошего не будет. Но если пометить с её помощью невозможные программные пути, то это откроет компилятору путь к генерации более быстрого кода. То есть при таком использовании программа может, во-первых, остаться верной, а, во-вторых, стать более быстрой.

sts>Напиши пожалуйста про эту функцию поподробнее, хотя бы с одним примером использования?

Функция __builtin_unreachable в gcc, clang, icc. Примеры есть в справке по этим компиляторам. Обычно ей затыкают именно недостижимый код во всяких хитрых ветвлениях, ассемблерных вставках или используют для обозначения функций, которые никогда не возвращают управление.
Но с помощью этой функции можно заставить компилятор делать некоторые интересные вещи. Например, можно написать вспомогательный макрос
#define assume(cond) do{ if (!(cond)) __builtin_unreachable(); } while(0)
Если условие ложно, то наступит UB, а следовательно компилятор может всегда считать, что условие истинно. Разумеется если исходное условие истинно, то внутрь управление не попадёт, а следовательно UB не будет, то есть программа останется верной. В общем поведение похоже на стандартный макрос assert, и бесстрашный программист может даже использовать assert в отладочной сборке, но заменять его на assume в оптимизированной.

Теперь банальный пример как можно это использовать: пусть у нас на входе есть натуральные числа и нужно найти остаток от деления на 4. Вроде просто:
int mod4(int a) {
    return a % 4;
}
Но проблема в том, что операция деления обрабатывает отрицательные числа несколько особым образом, что приводит к неожиданно длинному коду (на x86_64):
_Z4mod4i:
    movl    %ecx, %edx
    sarl    $31, %edx
    shrl    $30, %edx
    leal    (%rcx,%rdx), %eax
    andl    $3, %eax
    subl    %edx, %eax
    ret
Не то что бы он работал совсем медленно, но если сообщить компилятору что у нас действительно встречаются только натуральные числа
int mod4_nat(int a) {
    assume(a >= 0);
    return a % 4;
}
то код значительно упрощается:
_Z8mod4_nati:
    movl    %ecx, %eax
    andl    $3, %eax
    ret
Так как такая простая функция почти наверно будет встроена, то по сути от многострочной игры с битами останется одна ассемблерная инструкция. А код будет соответствовать простому выражению
return a & 3;
Сразу компилятор такую замену сделать не имел права, так как (a % 4) и (a & 3) дают разный результат для знаковых чисел (но одинаковый для беззнаковых).
Правда это всего лишь пример, так как в реальном коде можно было бы сразу написать
int mod4(int a) {
    return a & 3;
}
или даже
unsigned mod4(unsigned a) {
    return a % 4;
}
Но общий принцип использования assume именно таков, и можно попробовать сделать что-то другое:
int sum(const int array[], size_t n) {
    int accumulator = 0;
    assume(n > 0);
    for (size_t i = 0; i < n; ++i)
        accumulator += array[i];
    return accumulator;
}
Здесь компилятор решит, что тело цикла выполнится как минимум однажды, а следовательно можно переписать цикл на более эффективный аналог do {} while(...), где будет на одну проверку и переход меньше. Опять же код стал короче и быстрее. Но теперь обязанностью программиста станет следить чтобы массив никогда не был пуст.

Ну или можно попробовать сообщить компилятору базовые знания по свойству функций:
struct Vector2 {
    double x, y;
    Vector2(double xx, double yy) : x(xx), y(yy) {}
    Vector2(double angle) : x(cos(angle)), y(sin(angle)) {}
    
    double LengthSq() const {
        return x*x + y*y;
    }
    
    double Length() const {
        return sqrt(LengthSq());
    }
    
    void Normalize() {
        double l = Length();
        x /= l;
        y /= l;
    }
};
Класс — обычный вектор на плоскости. Но для вычислений часто нужно работать с вектором единичной длины: для некоторый формул это вопрос простоты, для других — правильности. Для приведения вектора к единичной длине существует очевидный метод Normalize, И какая-нибудь типичная функция может начинаться так:
Vector2 doSomething(Vector2 v) {
    v.Normalize();
    // используем v для вычислений
}
Но также очевидно что метод Normalize очень медленный — два деления да квадратный корень — это очень тяжело, но существенно по другому его реализовать нельзя (оставаясь в рамках достаточно точных вычислений, конечно). Особо стоит обратить внимание, что в некоторых случаях вектор имеет гарантированно известную длину (например, в результате работы конструктора Vector2(M_PI/17)), а в некоторых случаях — произвольную, но узнать внутри функции doSomething об этом нельзя. Зато можно оставить подсказки компилятору:
struct Vector2 {

    Vector2(double angle) : x(cos(angle)), y(sin(angle)) { assume(LengthSq() == 1); }

    void Normalize() {
        double l = Length();
        x /= l;
        y /= l;
        assume(LengthSq() == 1);
    }

        ...
};
Вот тут уже, в отличии от предыдущих примеров, всё почти правда — макрос накладывает условие не на входные параметры функции, а на её результат. А это значительно безопаснее и проще в использовании.
Теперь если написать
Vector2 redundantNormalize(Vector2 a) {
    a.Normalize();
    a.Normalize();
    return a;
}
то компилятор избавится от второго вызова метода, так в результате уже первого вызова будет получено нужно значение.
Аналогично в коде
Vector2 getDirection(double angle) {
    Vector2 u(angle);
    u.Normalize();
    return u;
}
компилятор выкинет вызов Normalize вообще, так как он не влияет на длину вектора.

Конечно, вряд ли в коде одной функции будет сделан вызов Normalize дважы подряд. Но ситуация, когда такой вызов делается в разных функциях вполне вероятна. И при межпроцедурной оптимизации компилятор сможет найти эти связи. То есть функция doSomething всегда будет безопасной, так как явно нормализует свой входной аргумент, но при вызове doSomething(Vector(angle)) компилятор сможет сделать более эффектиный код, даже несмотря на то, что значение переменной angle не известно на этапе компиляции.

Ну и на этом примере можно посмотреть что произойдёт если программист ошибётся в написании предпосылок:
Vector2(double angle) : x(cos(angle)), y(sin(angle)) { assume(LengthSq() == 0.25); }
Теперь это конечно UB. И при таких условиях функция getDirection после компиляции примет вид
Vector2 getDirection(double angle) {
    Vector2 u(angle);
    u.x += u.x;
    u.y += u.y;
    return u;
}
Вполне хорошая замена: вместо квадратного корня и делений получилась всего пара сложений. Но интересно, что ошибка изначально была в конструкторе, а проявилась при оптимизации Normalize, что собственно показывает, что если есть UB, то ломаться программа может в произвольном месте (наверно это аргумент с осторожностью относиться к идее всеобщей замены assert на assume).

Ну и проверял я это с gcc-4.7. Другие компиляторы могут вести (и ведут) себя по разному. Все таки это компилятор, а не автоматический прувер теорем, так что сложные условия, увы, писать практически бесполезно. Если следствие из подсказки нельзя получить буквально в одно действие, то она почти наверняка будет проигнорирована.
Re[16]: __builtin_unreachable
От: Evgeny.Panasyuk Россия  
Дата: 21.03.13 00:15
Оценка:
Здравствуйте, watch-maker, Вы писали:

WM>Не то что бы он работал совсем медленно, но если сообщить компилятору что у нас действительно встречаются только натуральные числа
int mod4_nat(int a) {
WM>    assume(a >= 0);
WM>    return a % 4;
WM>}
то код значительно упрощается:
_Z8mod4_nati:
WM>    movl    %ecx, %eax
WM>    andl    $3, %eax
WM>    ret
WM>


Это то о чём я мечтал — axiomatic_assert!
Пойду играться
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.