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

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

09.07.2017, 22:18. Показов 2688. Ответов 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
12938 / 6805 / 1821
Регистрация: 18.10.2014
Сообщений: 17,224
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
12938 / 6805 / 1821
Регистрация: 18.10.2014
Сообщений: 17,224
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
12938 / 6805 / 1821
Регистрация: 18.10.2014
Сообщений: 17,224
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
12938 / 6805 / 1821
Регистрация: 18.10.2014
Сообщений: 17,224
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
12938 / 6805 / 1821
Регистрация: 18.10.2014
Сообщений: 17,224
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
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru