Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.75/8: Рейтинг темы: голосов - 8, средняя оценка - 4.75
749 / 352 / 72
Регистрация: 10.06.2014
Сообщений: 2,371
1

Странный порядок вызова конструкторов и передача временного обьекта в функцию в качестве неконстантной ссылки

09.07.2017, 22:18. Показов 1657. Ответов 25
Метки нет (Все метки)

Есть код

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//g++  5.4.0
#include <iostream>
 
struct foo
{
    foo(int){std::cout << "int ctor" << std::endl;}
    foo() {std::cout << "default" << std::endl;}
    foo(const foo&){std::cout << "copy" << std::endl;}
    foo& operator=(const foo&){std::cout << "assign by ref" << std::endl; return *this;}
};
 
void bar(foo&)
{
    std::cout << "non const ref arg" << std::endl;
}
 
int main()
{
    bar(foo() = 5);
}
http://rextester.com/IQFWU37401
У меня по нему несколько вопросов:
1. Конструкция foo() = 5 генерирует временный объект, а временный обьект в функцию по ссылке можно передавать только если эта ссылка константная. Каким образом функция bar в данном коде принимает ссылку на временный обьект? Ведь ссылка то не константная!

2. Вызов конструктора, который принимает (int). Как такое происходит? Я бы понял если было бы написано foo f = 5; но тут написанно foo() = 5, то есть синтаксис, в котором вызывается конструктор по умолчанию. По идее этот код не должен компилироваться, потому что ведь выражение парсится справа на лево. То есть левосторонний объект foo() ещё не создан, а int конструктор почему то вызывается так, как будто этот объект создан(иначе в результате конструктор по умолчанию был бы вызван первым)

3. Порядок вызова конструкторов.
int ctor
default
assign by ref

Итого сработало 3 конструктора:
Тот что сработал первым думаю станет понятно почему если поможете разобраться с вопросом из п.2
Тот что сработал вторым - полагаю это при создании временного foo() который слева от 5

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

Надеюсь что кто нибудь поможет разобраться с этим...

Добавлено через 11 минут
А вот в этом коде создаётся 2 обьекта но вызывается три конструктора! В чем дело?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//g++  5.4.0
 
#include <iostream>
 
struct foo
{
    int n;
    foo(int){std::cout << "int ctor" << std::endl;}
    foo() {std::cout << "default" << std::endl;}
    foo(const foo&){std::cout << "copy" << std::endl;}
    foo(foo&&){std::cout << "moved" << std::endl;}
    foo& operator=(const foo&){std::cout << "assign by ref" << std::endl; return *this;}
};
 
int main()
{
    foo f;
    f = 5;
}
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
09.07.2017, 22:18
Ответы с готовыми решениями:

Порядок вызова конструкторов
на срр-reference нашёл тему про виртуальный деструктор, но я так и не понял (да там и не...

Порядок вызова конструкторов
Всем доброго дня. Наткнулся в коде на интересные грабли: test.cpp #include &quot;test.h&quot; Test...

Порядок вызова конструкторов
Есть классы First и Second. Класс Second наследуется от First. Я имею ввиду: class Second:...

Порядок вызова конструкторов/деструкторов
Вопрос чисто теоретический. Попробую сформулировать, не ругайте если получится коряво. Например,...

25
Заблокирован
09.07.2017, 22:20 2
Цитата Сообщение от Undisputed Посмотреть сообщение
2. Вызов конструктора, который принимает (int). Как такое происходит?
В выражении foo() = 5, foo(int) вызывается для 5, конструктор без аргументов вызывается для foo() а потом вызывается оператор присваивания.
1
Эксперт С++
8554 / 4130 / 908
Регистрация: 15.11.2014
Сообщений: 9,328
09.07.2017, 22:23 3
Цитата Сообщение от Undisputed Посмотреть сообщение
2. Вызов конструктора, который принимает (int). Как такое происходит?
C++
1
bar(foo() = 5);
рассмотрим поближе:
C++
1
foo() = 5
здесь обычным дефолтным конструктором создается временный объект,
которому присваивается циферка 5.

класс не умеет присваивать циферки,
зато умеет с них строится.

поэтому циферка 5 приводится к типу класса:

C++
1
foo() = foo(5)
далее отрабатывает оператор присвоения,
который возвращает обычную ссылку,
которую принимает bar
1
Заблокирован
09.07.2017, 22:23 4
Цитата Сообщение от Undisputed Посмотреть сообщение
Каким образом функция bar в данном коде принимает ссылку на временный обьект?
А в функцию bar временный объект и не передаётся. )
1
С чаем беда...
Эксперт CЭксперт С++
9257 / 4757 / 1288
Регистрация: 18.10.2014
Сообщений: 10,817
09.07.2017, 22:27 5
Лучший ответ Сообщение было отмечено Undisputed как решение

Решение

Цитата Сообщение от Undisputed Посмотреть сообщение
1. Конструкция foo() = 5 генерирует временный объект, а временный обьект в функцию по ссылке можно передавать только если эта ссылка константная. Каким образом функция bar в данном коде принимает ссылку на временный обьект? Ведь ссылка то не константная!
Нет такого правила "временный обьект в функцию по ссылке можно передавать только если эта ссылка константная" и никогда не было.

Есть правило "rvalue по обычной ссылке можно передавать только если эта ссылка константная". Например, выражение foo() порождает rvalue и передать его результат в функцию по обычной ссылке можно было бы только если бы эта ссылка была константной. "Временные объекты" часто попадают в это правило потому, что выражения, формирующие временные объекты, сами по себе обычно являются rvalue. Например, foo() - это rvalue.

Но у вас в коде в bar() передается не foo(), а foo() = 5. Результат foo() = 5 - это результат оператора присваивания. А он у вас уже возвращает lvalue типа int. Так как это lvalue, его можно передавать по неконстатной ссылке. А то, что это lvalue ссылается на временный объект никакой роли не играет.

Цитата Сообщение от Undisputed Посмотреть сообщение
2. Вызов конструктора, который принимает (int). Как такое происходит? Я бы понял если было бы написано foo f = 5; но тут написанно foo() = 5, то есть синтаксис, в котором вызывается конструктор по умолчанию. По идее этот код не должен компилироваться, потому что ведь выражение парсится справа на лево. То есть левосторонний объект foo() ещё не создан, а int конструктор почему то вызывается так, как будто этот объект создан(иначе в результате конструктор по умолчанию был бы вызван первым)
Конструктор вызывается для правой части присвавивания. Ваш код эквивалентен foo() = foo(5). Вот в правой части вы и видите ваш конструктор, который принимает (int).

Оператора присваивания для правой части типа int у вас в коде нет вообще. Поэтому этот код компилируется через преобразование правой части к foo при помощи вашего же конструктора преобразования.

Цитата Сообщение от Undisputed Посмотреть сообщение
3. Порядок вызова конструкторов.
int ctor
default
assign by ref

Итого сработало 3 конструктора:
Это где тут 3 конструктора??? Я вижу только два срабатывания конструкторов.

Цитата Сообщение от Undisputed Посмотреть сообщение
Тот что сработал первым думаю станет понятно почему если поможете разобраться с вопросом из п.2
Тот что сработал вторым - полагаю это при создании временного foo() который слева от 5

Но каким боком здесь оператор копирования присваиванием? Ведь он вызывается тогда, когда уже объект перемешается по ссылке в функцию.
Что за белибреда? Вы сами написали в коде опертор присваивания: foo() = 5. Вот он и вызывается!
2
Эксперт С++
8554 / 4130 / 908
Регистрация: 15.11.2014
Сообщений: 9,328
09.07.2017, 22:28 6
Цитата Сообщение от Undisputed Посмотреть сообщение
А вот в этом коде создаётся 2 обьекта но вызывается три конструктора! В чем дело?
тоже самое как и в первом случае:
C++
1
2
foo f;
f = foo(5);
Добавлено через 57 секунд
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Нет такого правила "временный обьект в функцию по ссылке можно передавать только если эта ссылка константная" и ниеогда не было.
временный объект это не rvalue?
0
Заблокирован
09.07.2017, 22:29 7
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Результат foo() = 5 уже lvalue типа int.
Типа foo&.
0
С чаем беда...
Эксперт CЭксперт С++
9257 / 4757 / 1288
Регистрация: 18.10.2014
Сообщений: 10,817
09.07.2017, 22:41 8
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Но у вас в коде в bar() передается не foo(), а foo() = 5. Результат foo() = 5 - это результат оператора присваивания. А он у вас уже возвращает lvalue типа int.
Поправка: А он у вас уже возвращает lvalue типа foo.

Цитата Сообщение от hoggy Посмотреть сообщение
временный объект это не rvalue?
Rvalue, rvalue и т.п. - это не свойства объектов. Это свойства выражений, дающих нам доступ к объекту. Два разных выражения могут давать нам доступ к одному и тому объекту и как к lvalue, и как к rvalue.

Никто не запрещает вам доступаться к временному объекту, как к lvalue, если вы сформируете соответствующее выражение, обеспечивающее такой доступ. Сама суть понятия "объект" в С++ ("область в хранилище") говорит, что к объекту, при желании, всегда можно получить lvalue-доступ (разве что битовые поля являются исключением).
3
749 / 352 / 72
Регистрация: 10.06.2014
Сообщений: 2,371
09.07.2017, 22:51  [ТС] 9
Всем большое спасибо за ответы, вроде разобрался с вашей помощью!

Но с передачей в функцию аргумента не до конца понял, это похоже на обман компилятора.
Несмотря на то что operator = возвращает ссылку на foo и это полностью соответствует сигнатуре функции bar, тем не менее, он все же rvalue, и если фактически передать тот же самый объект который формируется как rvalue, но только иным образом, то ведь не скомпилируется код!

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//g++  5.4.0
 
#include <iostream>
 
struct foo
{
 
};
 
void bar(foo&){}
 
int main()
{
    bar(foo());
}
То есть делаем одно и то же (конечный результат одинаковый), но в одном случае работает а в другом нет. Похоже на игру слов с компилятором
0
Заблокирован
09.07.2017, 23:03 10
Цитата Сообщение от Undisputed Посмотреть сообщение
тем не менее, он все же rvalue
rvalue (если ещё точнее, prvalue) являются результаты вызовов функций, возвращающих не ссылку.
Результат вызова функции, возвращающей lvalue-ссылку является lvalue.

operator=() возвращае lvalue-ссылку.

Добавлено через 5 минут
Если хочешь запретить вызывать operator=() у временных объектов, добавь к нему нужный ref qualifier.
1
Эксперт С++
8554 / 4130 / 908
Регистрация: 15.11.2014
Сообщений: 9,328
09.07.2017, 23:04 11
Цитата Сообщение от Undisputed Посмотреть сообщение
void bar(foo&){}
Цитата Сообщение от Undisputed Посмотреть сообщение
bar(foo());
функция принимает обычную ссылку.
foo() - это prvalue.
выражение, которое даёт доступ к "чисто pvalue",
ну или к "реально временному объекту", если попроще.

а pvalue к обычной ссылке не биндится.
только к константной.
1
С чаем беда...
Эксперт CЭксперт С++
9257 / 4757 / 1288
Регистрация: 18.10.2014
Сообщений: 10,817
09.07.2017, 23:06 12
Цитата Сообщение от Undisputed Посмотреть сообщение
То есть делаем одно и то же (конечный результат одинаковый), но в одном случае работает а в другом нет. Похоже на игру слов с компилятором
Ну преграды на привязывание обычной неконстантной ссылки к временному объекту - они сами по себе несколько искусственные. Почему константную ссылку привязывать можно, а неконстантную - нельзя? Понятно, что тут запросто можно наткнуться на проблемы с временем жизни привязанного объекта, но на такие же проблемы можно наткнуться и с константной ссылкой.

В общем, идея заключается в том, что привязать обычную неконстантную ссылку к временному объекту можно, но для этого надо сознательно сделать дополнительные телодвижения. Вот вы их и сделали. Правда, бессознательно.
1
749 / 352 / 72
Регистрация: 10.06.2014
Сообщений: 2,371
09.07.2017, 23:14  [ТС] 13
TheCalligrapher,
Ну по стандарту константная ссылка на временный объект продлевает время жизни этого объекта до тех пор, пока жива эта ссылка. Поэтому если явно не стрелять себе в ногу манипулируя адресами думаю проблем быть не должно т.к есть гарантии, что такой объект можно читать безопасно. Но про неконстантные ссылки на временные объекты все что я слышал это UB
0
С чаем беда...
Эксперт CЭксперт С++
9257 / 4757 / 1288
Регистрация: 18.10.2014
Сообщений: 10,817
09.07.2017, 23:24 14
Цитата Сообщение от Undisputed Посмотреть сообщение
Ну по стандарту константная ссылка на временный объект продлевает время жизни этого объекта до тех пор, пока жива эта ссылка.
Во-первых, это относится лишь к четко очерченному узкому набору контекстов. Во-вторых, если бы не существовало ограничения на привязку неконстантных ссылок к rvalue, это правило для временным объектов можно было бы распространить и на них.

Цитата Сообщение от Undisputed Посмотреть сообщение
Поэтому если явно не стрелять себе в ногу манипулируя адресами думаю проблем быть не должно т.к есть гарантии, что такой объект можно читать безопасно.
Для того, чтобы, например, сохранить такую константную ссылку в другой, долгоживущей, константной ссылке, совсем не надо манипулировать адресами. При желании, UB получается запросто, без каких-либо "хакерских" манипуляций.

Цитата Сообщение от Undisputed Посмотреть сообщение
Но про неконстантные ссылки на временные объекты все что я слышал это UB
Не знаю, где вы могли такое услышать - это совершенно не верно. Никакого UB в этом нет.
1
Эксперт С++
8554 / 4130 / 908
Регистрация: 15.11.2014
Сообщений: 9,328
09.07.2017, 23:25 15
Цитата Сообщение от Undisputed Посмотреть сообщение
Но про неконстантные ссылки на временные объекты все что я слышал это UB
нет никакого UB.

способ, который вы использовали
в самом нулевом посте - вполне себе законный.

что касается слухов, которые вы там слышали:
вероятно имелось ввиду,
что нельзя срисовать обычную ссылку с rvalue.
вот это - не по стандарту.

православные компиляторы (gcc например)
просто откажутся такое компилировать.

вижуал-студийный компилятор скушает.
но сделает предупреждение:
"задействовано нестандартное расширение компилятора".

что касается UB - все законно.
вам в любом случае нужно иметь ввиду,
временный объект сдохнет сразу же,
как только закончится породившее его выражение.
если только он не был прибинден к константной ссылке в той же области видимости,
что и породившее его выражение.
1
749 / 352 / 72
Регистрация: 10.06.2014
Сообщений: 2,371
09.07.2017, 23:34  [ТС] 16
TheCalligrapher,
А вот это разве не UB?
Создание не константной ссылки на временный объект

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//g++  5.4.0
 
#include <iostream>
 
int getNum()
{
    return 100;
}
 
int main()
{
    int& n = const_cast<int&>((const int&)(getNum()));
    std::cout << n;
}
0
Эксперт С++
8554 / 4130 / 908
Регистрация: 15.11.2014
Сообщений: 9,328
10.07.2017, 01:39 17
Цитата Сообщение от Undisputed Посмотреть сообщение
А вот это разве не UB?
UB.

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

вот только, когда все выражение завершается,
завершается время жизни и этой самой временной константной ссылки.
она дохнет.
дохнет и связанный с нею временный объект.

не константная ссылка по итогу смотрит на трупик.

Добавлено через 2 минуты
к тому же, вот здесь:
C++
1
 const_cast<int&>((const int&)(getNum()));
константность снимается с объекта,
который изначально был рожден константным.
а это уже - есть UB

нельзя снимать константность с объектов,
рожденных константными.
1
С чаем беда...
Эксперт CЭксперт С++
9257 / 4757 / 1288
Регистрация: 18.10.2014
Сообщений: 10,817
10.07.2017, 01:48 18
Цитата Сообщение от Undisputed Посмотреть сообщение
А вот это разве не UB?
Создание не константной ссылки на временный объект
Это UB, но совсем не из-за "создания неконстантной ссылки на временный объект". Это UB из-за попытки доступа к объекту после окончания его времени жизни.

Аналогичный пример

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
 
int getNum()
{
    return 100;
}
 
int main()
{
    const int &cn = getNum();
    int &n = const_cast<int &>(cn);
    std::cout << n  << std::endl;
}
тоже создает неконстантную ссылку на временный объект, но никакого UB не содержит.

Добавлено через 2 минуты
Цитата Сообщение от hoggy Посмотреть сообщение
константность снимается с объекта,
который изначально был рожден константным.
а это уже - есть UB
нельзя снимать константность с объектов,
рожденных константными.
Это почему это? Снимать константность всегда можно было с чего угодно и как угодно. Модифицировать константный объект нельзя, даже после снятия константности со ссылки, ибо это UB. Но пока попыток модификации нет, снятие константности само по себе никакого UB не порождает.
3
285 / 176 / 21
Регистрация: 16.02.2018
Сообщений: 666
18.02.2018, 02:09 19
Цитата Сообщение от hoggy Посмотреть сообщение
константность снимается с объекта,
который изначально был рожден константным.
Где вы нашли рождение константного объекта?
0
Эксперт С++
8554 / 4130 / 908
Регистрация: 15.11.2014
Сообщений: 9,328
18.02.2018, 13:55 20
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Но пока попыток модификации нет, снятие константности само по себе никакого UB не порождает.
верно. но важно понимать:
что такое "попытка модификации"?

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

вопрос:
1.
запуск такого метода для объекта рожденного константным - есть UB?
2.
или операции непосредственно записи в память объекта - есть UB?

формально - правильный ответ 2.
но на практике - правильный ответ 1.

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

C++
1
2
3
4
5
6
7
8
bool foo(class some&);
 
...
 
const some obj;
some& ref = const_cast<some&>(obj);
 
foo(ref);  //<--- отсутствие гарантий корректной работы - есть UB.
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
18.02.2018, 13:55

Классы, наследование, порядок вызова конструкторов
допустим у меня эсть два класса class a { publc: char *n; a() { n= new char ; } ~a()

Порядок вызова конструкторов при множественном наследовании
Здравствуйте, меня интересует вопрос, как изменить последовательность вызова конструкторов базовых...

Порядок вызова конструкторов при присваивании объектов одного класса
Имеется код ниже. Wein dres = rom; Где dres и rom объекты класса Wein. Класс Wein имеет...

Передача временного массива в качестве параметра функции
Здравствуйте, можно ли как то описать параметр функции, что бы передавать в нее не существующий, а...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2021, vBulletin Solutions, Inc.