LINUX.ORG.RU

Временные переменные в С++


0

0

Страйструп стр 302 утверждается что «Временная переменная уничтожается, когда „её! ссылка или именованный объект выходят из области видимости“ и что „Временная переменная может использоваться в качестве инициализатора константной ссылки или именованного объекта“. Если оба утверждения верны то:

1) следующее должно работать:

 #include <iostream>

void f1(int i) { std::cout << "hello " << i << std::endl;}

class C1
{
        int i;
        void (*F)(int);

        public:
        C1() {i =1; F=f1; std::cout << "C1 init constructor" << std::endl;}
        C1(int I) {i = I; F=f1; std::cout << "C1 int constructor" <<std::endl;}
        C1(const C1& c) {i = c.i; F=f1; std::cout << "C1 copy construcror" << std::endl;}
        C1& operator=(const C1& c ) {i = c.i; std::cout <<"C1 operator =" << std::endl; return *this;}
        ~C1() {F=0; std::cout << "C1 destructor " << i << std::endl; }
        C1 operator+(const C1& c) {; std::cout << "C1 operator+" <<std::endl; return C1(i + c.i);}
        C1& operator+(int ii) {i += ii; return *this;}
        void f() const {F(i);}
};

int main()
{
        C1 c1;
        C1 c2;
        const C1& c3 = (c1 + c2) + 1;
        C1 c4;
        c1.f();
        c2.f();
        c3.f();
        c4.f();
} 

что не правда:

$g++ -std=c++98 -pedantic -Wall -Wextra c++test13.cc
$./a.out
 C1 init constructor
C1 init constructor
C1 operator+
C1 int constructor
C1 destructor 3
C1 init constructor
hello 1
hello 1
Ошибка сегментации(core dumped)

Какого лешего деструктор вызывается так рано?

2) В следующем деструкторы должны вызываться в конце:

 -"-
int main()
{
        C1 c1;
        C1 c2;
        C1 c3 = (c1 + c2) + 1;
        C1 c4;
        c1.f();
        c2.f();
        c3.f();
        c4.f();
}  

что опять не правда:

$g++ -std=c++98 -pedantic -Wall -Wextra c++test13.cc
$./a.out
C1 init constructor
C1 init constructor
C1 operator+
C1 int constructor
C1 copy construcror
C1 destructor 3
C1 init constructor
hello 1
hello 1
hello 3
hello 1
C1 destructor 1
C1 destructor 3
C1 destructor 1
C1 destructor 1

Тут наверное текст перевели с ошибками либо в сам автор описался, так как ранее он урверждает что временная переменная существует до окнца полного выражения где она родилась, что тут собственно и происходит.

ЗЫ: А вот прикола с константной ссылкой я непонял. Может кто объяснит?

В частности пишут, что т. Бъярн назвиздел:

shaanxxx wrote:

scope of temporaries would be the block in which temporaries are defined.

Not correct:

«Unless bound to a reference or used to initialize a named object, a temporary object is destroyed at the end of the full expression in which it was created.» [Stroustrup]

that is in:

const char *t = (s+«def»).c_str();

the temp string created from s+«def» is destroyed at ';'

anonymous
()

Или нет, хз.

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

Немного не в тему. Где конец «полного выражения» мне вещь вполне известная. А вот почему когда Я константную ссылуку на Т инициализирую вресенной переменной типа Т эта временная переменная удаляется и создается новая мне не совсем понятно.

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

Оригинал:

Unless bound to a reference or used to initialize a named object, a temporary object is destroyed at the end of the full expression in which it was created. A full expression is an expression that is not a subexpression of some other expression.

The standard string class has a member function c_str() that returns a C-style, zero-terminated string array of characters (§3.5.1, §20.4.1). Also, the operator + is defined to mean string concatenation. These are very useful facilities for strings. However, in combination they can cause obscure problems. For example:

      void f(string s1, string& s2, string& s3)
      {
             const char* cs = (s1+s2).c _str();
             cout << cs;
             if (strlen(cs=(s2+s3).c_str())<8 && cs[0]== 'a') {
                      // cs used here
             }
      }

Probably, your first reaction is ‘‘but don’t do that,’’ and I agree. However, such code does get written, so it is worth knowing how it is interpreted.

A temporary object of class string is created to hold s1+s2. Next, a pointer to a C-style string is extracted from that object. Then – at the end of the expression – the temporary object is deleted. Now, where was the C-style string allocated? Probably as part of the temporary object holding s1+s2, and that storage is not guaranteed to exist after that temporary is destroyed. Consequently, cs points to deallocated storage. The output operation cout<<cs might work as expected, but that would be sheer luck. A compiler can detect and warn against many variants of this problem.

anonymous
()

Временные переменные создаются, когда инициализатор не lvalue. Он это пишет.

anonymous
()

В данном случае адрес взять можно у этого выражения "(c1 + c2) + 1;", значит временных объетов, предназначенных исключительно для инициализации ссылки, не создаётся. Используется временный объект (c1 + c2) + 1.

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

Ну уж давайте тогда оригинал двумя абзацами ниже по тексту - это раз. Почему этот временный объект уничтожается в то время как ссылка на него продолжает жить это два? Что все ходите вокруг вопроса да не в том направлении? Почему происходит сегфолт когда согласно Страуструпу все должно работать?

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

1) A temporary can be used as an initializer for a const reference or a named object. For example:

     
     void g(const strin g&, const string&);
     vo id h(string& s1, string s2)
     {
            const string& s = s1+s2;
            string ss = s1+s2;
            g(s,ss); // we can use s and ss here
     }

This is fine. The temporary is destroyed when ‘‘its’’ reference or named object go out of scope. Remember that returning a reference to a local variable is an error (§7.3) and that a temporary object cannot be bound to a non-const reference (§5.5).

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

anonymous
()

Это работает

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

int main()
{
	string s1="s1", s2="s2";
	const string& s = s1+s2;
	string ss = s1+s2;
	cout << s << " " << ss << endl;
	return 0;
}
---------------------------------
А тут компилятор ругается
#include <iostream>
#include <string>
using namespace std;

int main()
{
	string s1="s1", s2="s2";
	const string* s = &(s1+s2);
	string ss = s1+s2;
	cout << *s << " " << ss << endl;
	return 0;
}

c.cpp: In function ‘int main()’:
c.cpp:8: warning: taking address of temporary
anonymous
()

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

C1& operator+(const C1& c) {std::cout << "C1 operator+" <<std::endl; i += c.i; return *this;}

Тогда всё работает

C1 init constructor
C1 init constructor
C1 operator+
C1 init constructor
hello 3
hello 1
hello 3
hello 1
C1 destructor 1
C1 destructor 1
C1 destructor 3
anonymous
()
Ответ на: комментарий от anonymous

Рожденный вначале темы пример показывает что такого НЕ бывает: The temporary is destroyed when ‘‘its’’ reference or named object go out of scope. ременная переменная умирает НЕ когда ‘‘its’’ named object go out of scope а после полного выражения где она появилась (a temporary object is destroyed at the end of the full expression in which it was created). А в этом полном выражении она используется как фактический аргумент копирующего конструткора (иногда просто конструктора: при С3 с3 = с1 + с2; Вызывается обыкновенный конструктор а не копирующий). Так что на странице два утверждения друг другу противоречат.

То что компилятор проматерился на взятие адреса временной переменной... кто может удивляться удивитесь! Какого черта компилятор удаляет временную переменную которая является инициализатором константной ссылки? Именно по этому первый пример из первого поста сегфолтится, так быть не должно. Почему так происходит?

mskmsk1985
() автор топика
Ответ на: комментарий от mskmsk1985
#include <iostream>

void f1(int i) { std::cout << "hello " << i << std::endl;}

class C1
{
	int i;
	void (*F)(int);
	
	public:
		C1() {i =1; F=f1; std::cout << "C1 init constructor" << std::endl;}
		C1(int I) {i = I; F=f1; std::cout << "C1 int constructor" <<std::endl;}
		C1(const C1& c) {i = c.i; F=f1; std::cout << "C1 copy construcror" << std::endl;}
		C1& operator=(const C1& c ) {i = c.i; std::cout <<"C1 operator =" << std::endl; return *this;}
		~C1() {F=0; std::cout << "C1 destructor " << i << std::endl; }
		friend C1 operator+(const C1& c1, const C1& c2) {std::cout << "friend operator+" <<std::endl; return C1(c1.i + c2.i); }
		C1& operator+(int ii) {std::cout << "C1 operator+" <<std::endl; i += ii; return *this;}
		void f() const {F(i);}
};

int main()
{
	C1 c1;
	C1 c2;
	const C1& c3 = operator+(c1, c2) + 1;
	C1 c4;
	c1.f();
	c2.f();
	c3.f();
	c4.f();
} 
C1 init constructor
C1 init constructor
friend operator+
C1 int constructor
C1 operator+
C1 destructor 3
C1 init constructor
hello 1
hello 1
Segmentation fault

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

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

Понимаете ли в чем смысл траблы - 1) не долен временный объект уничтожаться когда им инициализируют константную ссылку и правила языка НИЧЕГО не говорят о том откуда этот временный объект взялся. 2) Константная ссылка инициализируемая уже созданным временным объектом в моем понимании не должна плодить вызовы конструкторов. 3) в примере ранее у Вас НЕТ ВРЕМЕННЫХ ОБЪЕКТОВ при инициализации константной ссылки (не подменяйте понятия)

Наконец последнее - по моим подозрениям в примере (где есть сегфолт) байда в том, что нарушается семантика оператора +, он обязан генерировать временные объекты для результата а не модифицировать один из своих операндов!

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

как написали, так он и делает. Выкиньте оператор

C1& operator+(int ii) {i += ii; return *this;} 

нафиг и живите счастливо. здесь он не нужен.

rha
()
#include <iostream>
#include <string>
using namespace std;

class str
{
	string* s;
public:
	str(string p) { cout << "constructor" << endl; s = new string(p); }
	~str() { cout << "destructor" << endl; delete s; }
	string Get() const {return *s;};
	str& operator+(string p) { cout << "operator+" << endl; *s += p; return *this;};
	friend ostream& operator << (ostream& os, const str& v) { os << *(v.s); return os;}
};

int main()
{
	const str& s = str("значение");// тут деструктор НЕ вызывается после окончания выражения
	//const str& s = str("значение") +"ee"; // тут деструктор вызывается после окончания выражения
	cout << "после ссылки" << endl;
	cout << s << endl;
	return 0;
}

const str& s = str(«значение»);

constructor
после ссылки
значение
destructor

const str& s = str(«значение») +«ee»;

constructor
operator+
destructor
после ссылки
Segmentation fault

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

Кажется я начитнаю понимать. Без вызова оператора + ссылка инициализируется ОБЪЕКТОМ, а с вызовом оператора + ссылка инициализируется ССЫЛКОЙ.

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

*const* str& operator+(string p) { cout << «operator+»<<endl; *s += p; return *this;};

anonymous
()
#include <iostream>
#include <string>
using namespace std;

class str
{
	string* s;
public:
	str(string p) { cout << "constructor"<<endl; s = new string(p); }
	~str() { cout << "destructor"<<endl; delete s; }
	const str& operator+(string p) { cout << "operator+"<<endl; *s += p; return *this;};
	friend ostream& operator << (ostream& os, const str& v) { os << *(v.s); return os;}
};

str func(string a)
{
	return str(a); // так работает
	//return str(a) + "ee"; //а если так, то Segmentation fault
}

int main()
{
	const str& s = func("значение");
	cout << "после ссылки" << endl;
	cout << s << endl;
	return 0;
}
anonymous
()
Ответ на: комментарий от anonymous

В правилах языка сказано что: 1) Ссылка есть ПОЛНЫЙ ЭКВИВАЛЕНТ тому объекту на который она указывает и СУЩЬНОСТИ ЯВЛЯЮЩЕЙСЯ ССЫЛКОЙ НЕ СУЩЕСТВУЕТ ЭТО ПРОСТО СИНОНИМ ИМЕНИ ДЛЯ НЕКОТРОГО ОБЪЕКТА так что не понял Я по поводу проведенной разницы между ССЫЛКОЙ и ОБЪЕКТОМ. 2) Это что за такой мега полезный + который вернет контанту? Это уже простите экзотика не отвечающая на поставленный вопрос.

ЗЫ: Объект не может быть удален коль на него существут ссылки или если оные могут быть созданы. Дык какого ... объект удаляется???

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

Почему-то при return str(a); деструктор вызывается в конце программы, а при return str(a) + 2; вызывается в конце выражения. Хотя в обоих случаях функция возвращает сам объект. Должна быть его копия там при возврате из функции и код ничего не должен знать в внутренностях функции. Но он всё зачем-то знает и ведёт себя по разному.

Может быть перемудрили с оптимизацией возвращаемого значения разаботчики GCC. Но у меня другого компилятора нет это проверить.

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

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

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

напонял, если честно. Вы начали разглагольствовать на тему, что «+» обязан генерировать временные объекты для результата а не модифицировать один из своих операндов!

убрав оператор, который я привёл выше, вы получите, что хотели явно.

rha
()

При возврате ссылки деструктор вызывается в конце выражения
str&/*ссылка*/ operator+(int p) { cout << «operator+» << endl; s += p; return *this;};

При возврате копии деструктор вызывается в конце программы
str/*копия*/ operator+(int p) { cout << «operator+» << endl; s += p; return *this;};

Таким образом если ссылка инициализируется ссылкой, то временный объект удаляется. Если ссылка инициализируется копией, то временный объект не удаляется. Вроде как это не противоречит тексту г. Страуструпа.

#include <iostream>

using namespace std;

class str
{
	int s;
public:
	str(int p) { cout << "constructor " << this << endl; s = p; }
	str(const str& p)  { cout << "copy constructor "<< this << endl; s = p.s; }
	str& operator=(const str& p) { cout << "operator="<<endl; s = p.s; return *this; }
	~str() { cout << "destructor " << this << endl; }
	str& operator+(int p) { cout << "operator+" << endl; s += p; return *this;};
	friend ostream& operator << (ostream& os, const str& v) { os << "out " << v.s; return os;}
};

int main()
{
	const str& s = str(3) + 2;
	cout <<"ref init done" << endl;
	cout << s << endl;
	return 0;
}
anonymous
()

Стандарт С++ в 12.2 «Temporary objects» говорит именно об временных объектах. В данном случае в выражении вычисляется временный объект, потом от него в выражении берётся ссылка, возвращаемая оператором + и этой ссылкой на временный объект (а не самим временным объектом!) инициализируется другая ссылка. И так как ссылка инициализирована ссылкой, а не объектом, то временный объект удаляется в конце выражения.

anonymous
()
#include <iostream> 
void f1(int i) { std::cout << "hello " << i << std::endl;} 
class C1 { 
  int i; 
  void (*F)(int); 
public: 
  C1() {i =1; F=f1; std::cout << "C1 init constructor" << std::endl;} 
  C1(int I) {i = I; F=f1; std::cout << "C1 int constructor" <<std::endl;} 
  C1(const C1& c) {i = c.i; F=f1; std::cout << "C1 copy construcror" << std::endl;} 
  C1& operator=(const C1& c ) {i = c.i; std::cout <<"C1 operator =" << std::endl; return *this;} 
  ~C1() {F=0; std::cout << "C1 destructor " << i << std::endl; } 
  C1 operator+(const C1& c) {; std::cout << "C1 operator+" <<std::endl; return C1(i + c.i);} 
  C1& operator+(int ii) {i += ii; return *this;} 
  void f() const {F(i);} 
};
int main() {
  C1 c1; 
  C1 c2; 
  const C1& c3 = (c1 + c2) + 1; 
  C1 c4; 
  c1.f(); 
  c2.f(); 
  c3.f(); 
  c4.f(); 
}

Не понял, в чём проблема.

Вычисляется временный объект CTemp как сумма c1+c2.

Затем к нему прибавляется единица и возвращается снова он же (C1::operator+(int ii)).

Затем он удаляется, ибо временный. При этом f в нём зануляется.

Затем его пытаются вызвать.

Оно должно работать? C++ сборщика мусора не имеет.

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

Еще раз нет никакой разницы в С++ между временным объектом и ссылкой на него (Вообще может не быть объекта представляющего собой ссылку, есть значение ссылки, сама сссылка лишь лексический синоним для другого имени (в частности для временного объекта если исходного имени нет.)). Если константная ссылка инициальизируется временным объектом он долже жить не до конца выражения а до конца области видимости константной ссылки.

mskmsk1985
() автор топика
Ответ на: комментарий от Miguel

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

mskmsk1985
() автор топика
Ответ на: комментарий от Miguel
#include <iostream>
using namespace std;
class C
{
public:
	C() { cout << "constructor" << endl; }
	~C() { cout << "destructor" << endl; }
};
int main()
{
	{
		const C& p = C();
		cout << "after ref" << endl;
	}
	cout << "----------------" << endl;
	{
		const C& p = (const C&)C();
		cout << "after ref" << endl;
	}	
	return 0;
}
constructor
after ref
destructor
----------------
constructor
destructor
after ref

У Вас operator+(int ii) возвращает ссылку, а не объект. Вышеприведенный пример показывает, что происходит, когда возвращают ссылку.
Запишите

C1 /*без ссылки!*/ operator+(int ii) {i += ii; return *this;} 
и оно заработает. Там появится вызов копирующего конструктора.

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

Если в этом моём сообщении http://www.linux.org.ru/jump-message.jsp?msgid=4524443&cid=4525774 программа, по Вашему мнению, работает неправильно, то проверьте на других компиляторах. Мне кажется, что тут всё законно и по стандарту и должно быть точно также и на других компиляторах. Ранее я объяснял про возврат ссылки на объект вместо объекта.

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

Также можно заметить, что во втором примере из 1-го поста деструктор для объекта 3 вызывается дважды: 1) в конце полного выражения 2) при выходе из области видимости.

Если подумать, то станет ясно, что компилятор не знает о том, что оператор +(int), вызываемый для временного объекта возвращает ссылку на этот же объект. Итого, имеем временный объект, который _не_ служит для инициализации константной ссылки, а значит уничтожается в конце полного выражения, и _неконстантную_ ссылку на этот объект, которой инициализируется константная ссыла. Для последней при выходе из области видимости вызывается деструктор.

P.S. Другой anonymous

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

Увы у меня тоже нет других компиляторов :(. Поставлю вопрос по другому (поймите правильно хочется разобраться) в случае когда + возвращает T& происходит что: 1) Инициализация константной сылки 2) тривиальное неявное преобразование T& в const T&?

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

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

Я про это уже говорил имея ввиду арушение семантики оператора +, но проблема с таким рассуждением в том что для оператора += с нужной семантикой (возврат ссылки ) трабла повторяется.

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

Возврат ссылки это замаскированный возврат адреса объекта.

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

Последний простой пример очень понятно показывает ситуацию на практике.

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

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

rha
()

(всю тему не читал)
по стандарту временный объект удаляется не раньше конца выражения и не позже конца блока
и инициализация ссылки ссылкой не имеет отношения к инициализации временным объектом (вроде уже сказали)

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

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

это интересная концепция, я бы назвал ее статической ссылкой, но к ссылке из с++ она отношения не имеет

плюсовая ссылка — это такой урезанный указатель

д-во:

int f(int&);
int x=1;
int y=2;
int& a = x;
int& b = y;
int z = f( random() < 0.5 ? a : b );

однако по причине урезанности у ссылки появляются некоторые новые свойства, например (у константной ссылки) удерживать временный объект

www_linux_org_ru ★★★★★
()

Вот почищенный вариант:

#include <iostream>

class C
{
public:
  C(int i): i(i), s("live") { std::cout << "ctor: " << i << std::endl; }
  C operator+(const C& c)  { std::cout << i << "+" << c.i << std::endl; return C(i+c.i); }
  C& operator+(int ii) { std::cout << i << "+" << ii << std::endl; i += ii; return *this; }
  ~C() { s="dead"; std::cout << "dtor: " << i << std::endl; }
  void info() const { std::cout << "info: " << i << " " <<s << std::endl; }
private:
  int i;
  const char* s;
};
int main()
{
  C c1(1);
  C c2(2);
  const C& c3 = (c1+c2);
  const C& c4 = (c1+c2)+5;
  c1.info();
  c2.info();
  c3.info();
  c4.info();
#ifdef TRY
  const C& c5 = c3+2;
  c5.info();
#endif
  return 0;
}

и каков вывод?

- компилятор Хорошего Языка должен варнинговать определение C& operator+(int ii)?

- компилятор Хорошего Языка должен варнинговать инициализацию константной ссылки неконстантной ссылкой?

- ?

www_linux_org_ru ★★★★★
()

выхлоп:

ctor: 1
ctor: 2
1+2
ctor: 3
1+2
ctor: 3
3+5
dtor: 8
info: 1 live
info: 2 live
info: 3 live
info: 8 dead
dtor: 3
dtor: 2
dtor: 1

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