LINUX.ORG.RU

const ссылка на временный объект

 


0

2

Подскажите, это в современных стандартах законно?

#include <iostream>
struct A {
        void boo() const {
                std::cout<<"I am alive"<<std::endl;
        }
};
 
const A& foo() {
        return A();
}
int main(void) {
        const A& a = foo();
        a.boo();
        return 1;
}
Метод boo отрабатывает, но вот объект разрушается раньше, тогда вопрос, почему отрабатывает метод? Если я захочу создать и вывести пооле класса, у меня уже идет segmentation fault. Читал в блоге у алены си пи пи, что все ок типа, но статья старая.



Последнее исправление: da17 (всего исправлений: 1)

итал в блоге у алены си пи пи, что все ок типа

Разработчики стандартных библиотек добавили в свой workflow санитайзеры в 2013 году, поэтому в стандартных библиотеках C++ меньше уязвимостей чем в Rust…

https://gcc.godbolt.org/z/oTf5EY38r

fsb4000 ★★★★★
()

Подскажите, это в современных стандартах законно?

Конечно же нет. Ни в современных, ни в каких.

Метод boo отрабатывает, но вот объект разрушается раньше, тогда вопрос, почему отрабатывает метод?

А почему ему не отрабатывать?

Читал в блоге у алены си пи пи, что все ок типа, но статья старая.

Не надо читать всякую ламерню.

slovazap ★★★★★
()

Метод boo отрабатывает, но вот объект разрушается раньше, тогда вопрос, почему отрабатывает метод?

Потому что это undefined behavior - т.е. стандарт не определяет, как себя должна при это вести программа.

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

rumgot ★★★★★
()
Ответ на: комментарий от da17

Методы класса - это обычные функции, что ты хочешь очищать?

moonmadness
()
Ответ на: комментарий от da17

А почему тогда память где остается реализация методов класса не очищается?

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

В твоём примере ты вообще не обращаешься к this, и состояние объекта ни на что не влияет. А мог бы даже успешно обратиться к полю, поскольку память где был расположен объект могла остаться доступной программе и даже не быть кем-то переписанной. В другом случае мог бы прочитать мусор, в третьем упасть, в четвёртом это даже не скомпилируется.

slovazap ★★★★★
()
Последнее исправление: slovazap (всего исправлений: 5)
Ответ на: комментарий от da17

Ну и что? Ты хочешь разобраться в том, как именно реализовано неопределенное поведение в разных компиляторах? Может будем учиться писать правильно, а не писать неправильно и разбираться, что там будет происходить?

rumgot ★★★★★
()
Ответ на: комментарий от invy

Да.

Нет.

Const reference продлевает жизнь обьекта.

До возврата из функции return. Иначе, например, можно было бы совершенного легально передавать ссылки на локальные переменные.

anonymous
()
Ответ на: комментарий от da17

Ничего непонятно

Я понял, у тебя интернета нет, кроме лора. Ты не можешь перейти на годболт.

const ссылка на временный объект (комментарий)

https://gcc.godbolt.org/z/oTf5EY38r

<source>: In function 'const A& foo()':
<source>:9:16: warning: returning reference to temporary [-Wreturn-local-addr]
    9 |         return A();
      |                ^~~

/app/example.cpp:9:18: runtime error: reference binding to null pointer of type 'const struct A'
/app/example.cpp:13:14: runtime error: member call on null pointer of type 'const struct A'
fsb4000 ★★★★★
()
Ответ на: комментарий от invy

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details

Но если прочитать про этот reference initialization, то можно выяснить, что:

There are following exceptions to this lifetime rule:

  • a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such return statement always returns a dangling reference.
aldaril_kote
()

Все будет работать, если заменишь на:

A foo() {
    return A{};
}
PRN
()

ну в общих чертах такой код лишен смысла — ибо к чему возвращать временный объект по ссылке, тем более по константной?

по поводу отработки метода — он принадлежит классу, а не объекту класса, — на него поинтер всегда существует, это просто участок кода, а не стейт объекта — в ассемблере это просто jmp обычно, ну или инлайн.

safocl ★★
()
Последнее исправление: safocl (всего исправлений: 1)
Ответ на: комментарий от fsb4000

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

da17
() автор топика
Последнее исправление: da17 (всего исправлений: 1)
Ответ на: комментарий от invy

Как же продлевает, если у меня мусор в членах класса. Компилирую gcc с опцией -std=c++11 как-то так

da17
() автор топика

На вопрос уже ответили, я не помешаю доп вопросом со «звездочкой»? Что за хитропопую оптимизацию делает вектор #1?

#include <vector>
#include <cstdio>
#include <cstdlib>
using namespace std;


void* operator new(std::size_t sz)
{
    std::printf("global op new called, size = %zu\n", sz);
    if (sz == 0)
        ++sz;
    if (void *ptr = std::malloc(sz))
        return ptr;
    throw std::bad_alloc{};
}

int main()
{
	// #1
	//global op new called, size = 80
	vector<int> v;
	v.reserve(20);
	v = {3, 4};
	
	// #2
	//global op new called, size = 80
	//global op new called, size = 8
	vector<int> v2;
	v2.reserve(20);
	v2 = vector<int>{3, 4};

	return 0;
}

Почему не создаётся промежуточный вектор? Т.е. получается, что второй раз конструктор отрабатывает на уже инициализированном векторе.

anonymous
()
Ответ на: комментарий от anonymous

Почему не создаётся промежуточный вектор?

Открой для себя: https://cppinsights.io/

cppinsights подскажет, что вызываются

constexpr vector& operator=( std::initializer_list<T> ilist );

и

constexpr vector& operator=( vector&& other ) noexcept

https://en.cppreference.com/w/cpp/container/vector/operator%3D

fsb4000 ★★★★★
()

Метод boo отрабатывает, но вот объект разрушается раньше, тогда вопрос, почему отрабатывает метод?

Видите ли, если вы для class создадите объект с помощью malloc вместо new, то код работать будет, но частенько и crash будет в непонятно какие моменты …

anonymous
()
Ответ на: комментарий от fsb4000

Так constexpr вектора пока только лишь мелкомягкие запилили, это не мой случай и такое не работает - constexpr vector i{4}; Да не ясно почему в разных случаях (#1, #2) поведение разное.

anonymous
()
Ответ на: комментарий от da17

Ну ты умкдрился выбрать вариант где не продлевает :)

invy ★★★★★
()
Ответ на: комментарий от anonymous

Почитай про move. У тебя случается аллокация как раз для временного вектора. Но когда ты его присваиваешь переменной, происходит тупо свап указателей. В итоге временный вектор становится пустым (точнее в нем буфер на 20 элементов от старого вектора, но size == 0, а через мгновение вызовется деструктор), а вектор в переменной использует его память.

Даже без move непонятно, зачем старому вектору делать аллокацию, если ты сделал reserve на 20 элементов, а присваиваешь вектор из 2 элементов. Даже если случится обычный copy (в C++ до 11 версии), переаллокация памяти не нужна.

Constexpr для этого совсем не нужен.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 3)
Ответ на: комментарий от da17

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

anonymous
()
Ответ на: комментарий от KivApple

Прежде чем делать move, временный вектор нужно создать, а это аллокация как ни крути, которая отметится в операторе new (но нет). Единственное объяснение (которое мне на ум приходит) - вектор создается в компайлтайме (constexpr), но ГЦЦ со Шлангом этого пока не умеют. Также не ясно - почему в первом случае «constexpr», а во втором нет, я лишь записал там в explicit форме то же самое. К тому же всего одна аллокация и в таком случае:

	int a; 
	cin >> a;
	// #1
	//global op new called, size = 80
	vector<int> v;
	v.reserve(20);
	v = {a};

Никакой constexpr здесь быть не может.

anonymous
()
Ответ на: комментарий от anonymous

Так constexpr вектора пока только лишь мелкомягкие запилили

При чём тут это?

Да не ясно почему в разных случаях (#1, #2) поведение разное.

Тебе ясно показали, почему. Просто ты довн.

anonymous
()
Ответ на: комментарий от anonymous

Никакой constexpr здесь быть не может.

constexpr int f(int i) { return i; }

#include <iostream>
using namespace std;

int main()
{
	int a;
	cin >> a;
	return f(a); // и здесь тоже не может?
}
anonymous
()
Ответ на: комментарий от anonymous

Ладно, похоже я не в том месте вопрос задал. Люди вообще не понимают темы компайл тайм строк и векторов, срабатывают лишь какие-то рефлексы на знакомые слова. Мне не вопросы надо задавать, а ликбез проводить.

anonymous
()
Ответ на: комментарий от anonymous

Ладно, похоже я не в том месте вопрос задал.

Ты на https://cppinsights.io/ перешёл или нет?

Что за люди то тут такие, не могут по одной ссылке перейти. Что оп, что ты.

Вот как твой main выглядит в cppinsight.

int main()
{
  vector<int> v = std::vector<int, std::allocator<int> >();
  v.reserve(20);
  v.operator=(std::initializer_list<int>{3, 4});
  vector<int> v2 = std::vector<int, std::allocator<int> >();
  v2.reserve(20);
  v2.operator=(std::vector<int, std::allocator<int> >{std::initializer_list<int>{3, 4}, std::allocator<int>()});
  return 0;
}

Теперь то понятно, почему std::vector во втором случае создаётся, а в первом нет?

И в моём первом ответе нужно было смотреть не на constexpr(он есть у обоих перегрузок оператора =), а на тип который он принимает.

std::initializer_list<T> ilist

и

vector&& other
fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Теперь то понятно, почему std::vector во втором случае создаётся, а в первом нет?

Нет, не понятно. Запись вида

v.operator=(std::initializer_list<int>{3, 4});

«трансформируется» точно в такую же:

v2.operator=(std::vector<int, std::allocator<int> >{std::initializer_list<int>{3, 4}, std::allocator<int>()});

Вызов конвертирующего конструктора с последующем перемещенимем. Первая форма - сокращение от второй, т.к. позволено из отсутствия explicit у конструктора. Единственный фокус провернуть такую - штуку - создать вектор в компайл тайме, а потом лишь скопировать (пройдёт мимо моего оператора new, хотя пример с cin не подтверждает это). Короче не знаю что за фокусы такие происходят.

anonymous
()
Ответ на: комментарий от anonymous

«трансформируется» точно в такую же

Нет. Initializer list это просто два указателя.

https://github.com/microsoft/STL/blob/main/stl/inc/initializer_list#L21-L53

трансформируется в «такую»:

int __temp_arr[2] = {3,4};
std::initializer_list<int> __temp_list(__temp_arr, __temp_arr + 2);
v.operator=(__temp_list);

или такую

int __temp_arr[2] = {3,4};
std::initializer_list<int> __temp_list(__temp_arr, 2);
v.operator=(__temp_list);
fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 2)
Ответ на: комментарий от fsb4000

Понял, не заметил (честно говоря и не ожидал, что у вектора есть такая сигнатура:

vector& operator=( std::initializer_list<T> ilist );

Какое-то туннельное мышление, инициализер лист плотно ассоциировался с конструктором, никак не с оператором =. Хотя логично проверить было сразу, но шоры …

Спасибо.

anonymous
()

const ссылка на временный объект

Жуть!

anonymous
()
Ответ на: комментарий от anonymous

Я про второй случай. Тут у тебя вполне есть две аллокации.

//global op new called, size = 80
	//global op new called, size = 8

В первом случае вектор вообще не создаётся. У тебя там std::initializer_list, а он создаётся статически. Constexpr для этого не нужен.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от aldaril_kote

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details

Но если прочитать про этот reference initialization, то можно выяснить, что:

There are following exceptions to this lifetime rule:

  • a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such return statement always returns a dangling reference.

А если почитать стандарт, то можно выяснить, что функции не возвращают ссылок. Ни dangling, ни не dangling.

anonymous
()
Ответ на: комментарий от x86-

Нихрена нельзя. Надо понимать одно: в c++ на уровне языка нет никакой мув-семантики. Она реализуется библиотечно, функциями. Для компилятора это просто особый тип ссылки, type&&, к которому автоматически преобразовывается выражение не являющееся именем переменной, и которая может быть отправлена в функцию принимающую type&&. Все кажущиеся логичными предположения о том как должен работать мув являются неверными. Все разговоры о том, что рвалуе ссылка чего-то там продлевает - неверны. Точнее они верны но только в узком смысле, продление жизни происходит только в достаточной степени чтоб хватило на передачу, на выход из функции это не распространяется. Более того, результат вызова функции, возвращающей объект по значению как раз является временным неименованным объектом и потому автоматически кастится к рвалуе ссылке, так что возврат рвалуе значения в принципе не имеет смысла, за исключением передачи назад рвалуе ссылки полученной на входе. Таким образом если тебе хочется создать объект в функции и передать его наружу мувом - просто возвращай по значению.

khrundel ★★★★
()
27 сентября 2021 г.

Настигла меня карма, на собеседовании дали этот вопрос, а все что я мог сказать, только то, что «так нельзя делать». Не разобрался до конца, потом еще начали const убирать добавлять и т.д. прося объяснить, что будет.

da17
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.