Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.92/13: Рейтинг темы: голосов - 13, средняя оценка - 4.92
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700

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

09.07.2017, 22:18. Показов 2523. Ответов 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)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
09.07.2017, 22:18
Ответы с готовыми решениями:

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

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

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

25
Заблокирован
09.07.2017, 22:20
Цитата Сообщение от Undisputed Посмотреть сообщение
2. Вызов конструктора, который принимает (int). Как такое происходит?
В выражении foo() = 5, foo(int) вызывается для 5, конструктор без аргументов вызывается для foo() а потом вызывается оператор присваивания.
1
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
09.07.2017, 22:23
Цитата Сообщение от 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
Цитата Сообщение от Undisputed Посмотреть сообщение
Каким образом функция bar в данном коде принимает ссылку на временный обьект?
А в функцию bar временный объект и не передаётся. )
1
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
12919 / 6787 / 1817
Регистрация: 18.10.2014
Сообщений: 17,174
09.07.2017, 22:27
Лучший ответ Сообщение было отмечено 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
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
09.07.2017, 22:28
Цитата Сообщение от Undisputed Посмотреть сообщение
А вот в этом коде создаётся 2 обьекта но вызывается три конструктора! В чем дело?
тоже самое как и в первом случае:
C++
1
2
foo f;
f = foo(5);
Добавлено через 57 секунд
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Нет такого правила "временный обьект в функцию по ссылке можно передавать только если эта ссылка константная" и ниеогда не было.
временный объект это не rvalue?
0
Заблокирован
09.07.2017, 22:29
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Результат foo() = 5 уже lvalue типа int.
Типа foo&.
0
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
12919 / 6787 / 1817
Регистрация: 18.10.2014
Сообщений: 17,174
09.07.2017, 22:41
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Но у вас в коде в bar() передается не foo(), а foo() = 5. Результат foo() = 5 - это результат оператора присваивания. А он у вас уже возвращает lvalue типа int.
Поправка: А он у вас уже возвращает lvalue типа foo.

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

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

Но с передачей в функцию аргумента не до конца понял, это похоже на обман компилятора.
Несмотря на то что 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
Цитата Сообщение от Undisputed Посмотреть сообщение
тем не менее, он все же rvalue
rvalue (если ещё точнее, prvalue) являются результаты вызовов функций, возвращающих не ссылку.
Результат вызова функции, возвращающей lvalue-ссылку является lvalue.

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

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

а pvalue к обычной ссылке не биндится.
только к константной.
1
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
12919 / 6787 / 1817
Регистрация: 18.10.2014
Сообщений: 17,174
09.07.2017, 23:06
Цитата Сообщение от Undisputed Посмотреть сообщение
То есть делаем одно и то же (конечный результат одинаковый), но в одном случае работает а в другом нет. Похоже на игру слов с компилятором
Ну преграды на привязывание обычной неконстантной ссылки к временному объекту - они сами по себе несколько искусственные. Почему константную ссылку привязывать можно, а неконстантную - нельзя? Понятно, что тут запросто можно наткнуться на проблемы с временем жизни привязанного объекта, но на такие же проблемы можно наткнуться и с константной ссылкой.

В общем, идея заключается в том, что привязать обычную неконстантную ссылку к временному объекту можно, но для этого надо сознательно сделать дополнительные телодвижения. Вот вы их и сделали. Правда, бессознательно.
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
09.07.2017, 23:14  [ТС]
TheCalligrapher,
Ну по стандарту константная ссылка на временный объект продлевает время жизни этого объекта до тех пор, пока жива эта ссылка. Поэтому если явно не стрелять себе в ногу манипулируя адресами думаю проблем быть не должно т.к есть гарантии, что такой объект можно читать безопасно. Но про неконстантные ссылки на временные объекты все что я слышал это UB
0
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
12919 / 6787 / 1817
Регистрация: 18.10.2014
Сообщений: 17,174
09.07.2017, 23:24
Цитата Сообщение от Undisputed Посмотреть сообщение
Ну по стандарту константная ссылка на временный объект продлевает время жизни этого объекта до тех пор, пока жива эта ссылка.
Во-первых, это относится лишь к четко очерченному узкому набору контекстов. Во-вторых, если бы не существовало ограничения на привязку неконстантных ссылок к rvalue, это правило для временным объектов можно было бы распространить и на них.

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

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

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

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

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

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

что касается UB - все законно.
вам в любом случае нужно иметь ввиду,
временный объект сдохнет сразу же,
как только закончится породившее его выражение.
если только он не был прибинден к константной ссылке в той же области видимости,
что и породившее его выражение.
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
09.07.2017, 23:34  [ТС]
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
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
10.07.2017, 01:39
Цитата Сообщение от Undisputed Посмотреть сообщение
А вот это разве не UB?
UB.

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

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

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

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

нельзя снимать константность с объектов,
рожденных константными.
1
Вездепух
Эксперт CЭксперт С++
 Аватар для TheCalligrapher
12919 / 6787 / 1817
Регистрация: 18.10.2014
Сообщений: 17,174
10.07.2017, 01:48
Цитата Сообщение от 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
Цитата Сообщение от hoggy Посмотреть сообщение
константность снимается с объекта,
который изначально был рожден константным.
Где вы нашли рождение константного объекта?
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
18.02.2018, 13:55
Цитата Сообщение от 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
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
18.02.2018, 13:55
Помогаю со студенческими работами здесь

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

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

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

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

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


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

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru