LINUX.ORG.RU

[c++] так все-таки баг языка или компилятора? (у кого есть icc, посмотрите)

 


0

0

В случае op_x и op_y копилятор, ИМХО, позволяет создать неконстантную ссылку на временный объект, который затем деструктируется. В совершенно аналогичном им op_z компилятор это не позволяет.

Выхлоп показывает, что с4 и с5 — это ссылки на мертвые объекты (т.е. объекты, у которых отработал деструктор)

#include <iostream>
using namespace std;

class C
{
public:
  C(int i): i(i), s("live") { cout << "ctor: " << i << endl; }
  C operator+(const C& c)  { cout << "plus: " << i << "+" << c.i << endl; return C(i+c.i); }
  C& operator+(int ii) { cout << "op_x: " << i << "+" << ii << endl; i += ii; return *this; }
  C& op_y(int ii) { cout << "op_y: " << i << "+" << ii << endl; i += ii; return *this; }
  friend C& op_z(C& that, int ii) {
    cout << "op_z: " << that.i << "+" << ii << endl;
    that.i += ii;
    return that;
  }
  void info() const { cout << "info: " << i << " " << s << endl; }
  ~C() { s="dead"; cout << "dtor: " << i << 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)+1;
  const C& c5 = (c1+c2).op_y(2);
  c1.info();
  c2.info();
  c3.info();
  c4.info();
  c5.info();
#ifdef C6
  const C& c6 = c3+3;
  c6.info();
#endif
#ifdef C7
  const C& c7 = op_z(c1+c2, 4);
  c7.info();
#endif
  return 0;
}

$ g++ -DC6 -Wall b.cxx
b.cxx: In function ‘int main()’:
b.cxx:35: error: passing ‘const C’ as ‘this’ argument of ‘C& C::operator+(int)’ discards qualifiers
$ g++ -DC7 -Wall b.cxx
b.cxx: In function ‘int main()’:
b.cxx:39: error: invalid initialization of non-const reference of type ‘C&’ from a temporary of type ‘C’
b.cxx:11: error: in passing argument 1 of ‘C& op_z(C&, int)’
$ g++  -Wall b.cxx
$ ./a.out
ctor: 1
ctor: 2
plus: 1+2
ctor: 3
plus: 1+2
ctor: 3
op_x: 3+1
dtor: 4
plus: 1+2
ctor: 3
op_y: 3+2
dtor: 5
info: 1 live
info: 2 live
info: 3 live
info: 4 dead
info: 5 dead
dtor: 3
dtor: 2
dtor: 1
$

**/
★★★★★

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

Язык такой. Так будет работать:

#include <iostream>
using namespace std;

class C
{
public:
  C(int i): i(i), s("live") { cout << "ctor: " << i << endl; }
  C operator+(const C& c)  { cout << "plus: " << i << "+" << c.i << endl; return C(i+c.i); }
  C& operator+(int ii) { cout << "op_x: " << i << "+" << ii << endl; i += ii; return *this; }
  C& op_y(int ii) { cout << "op_y: " << i << "+" << ii << endl; i += ii; return *this; }
  friend C& op_z(C& that, int ii) {
    cout << "op_z: " << that.i << "+" << ii << endl;
    that.i += ii;
    return that;
  }
  void info() const { cout << "info: " << i << " " << s << endl; }
  ~C() { s="dead"; cout << "dtor: " << i << 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+1);
  const C& c5 = C((c1+c2).op_y(2));
  c1.info();
  c2.info();
  c3.info();
  c4.info();
  c5.info();
  return 0;
}

nozh
()

icpc (ICC) 11.1 20091012

$ ./a.out
ctor: 1
ctor: 2
plus: 1+2
ctor: 3
plus: 1+2
ctor: 3
op_x: 3+1
dtor: 4
plus: 1+2
ctor: 3
op_y: 3+2
dtor: 5
info: 1 live
info: 2 live
info: 3 live
info: 5 dead
info: 5 dead
dtor: 3
dtor: 2
dtor: 1

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

> Язык такой. Так будет работать:

За исключением того, что у тебя будет c5.i==6, а в моем варианте было c5.i==5 :-)

______________________________

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

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

конечно оффтопик, но меня немного смущает, что сложение со скаляром семантически неодназначно.

  C(int i): i(i), s("live") { cout << "ctor: " << i << endl; } 
  C operator+(const C& c)  { cout << "plus: " << i << "+" << c.i << endl; return C(i+c.i); } 
  C& operator+(int ii) { cout << "op_x: " << i << "+" << ii << endl; i += ii; return *this; }

С одной стороны перегружается оператор +(C, int) с другой стороны перегружается +(C,C) и конструктор C(int).

В первом случае мы получаем C&, во втором C.

Почему бы не убрать оператор +(C, int)?

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

> info: 5 dead

info: 5 dead

интересно, что в отличие от gcc icc юзает для временной переменной ту же ячейку памяти

но меня больше варнинги интересуют, если их включить по максимуму (или даже вроде у icc есть какой-то статический анализ кода?)

www_linux_org_ru ★★★★★
() автор топика

8.5.3 References 1) A variable declared to be a T&, that is reference to type T (8.3.2), shall be initialized by an object, or function, of type T or by an object that can be converted into a T.

Но стандарт не говорит о том, что объект, которым инициализорована ссылка, должен жить столько, сколько живёт ссылка. Это не задача компилятора. Это задача программиста.

int main()
{
	char* p=0;
	char& r=*p;
	return 0;
}

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

> Почему бы не убрать оператор +(C, int)?

Предположим, что +(C, int) создан молодым (слишком) энергичным программистом с целью оптимизации. И почему компилятор не дает ему по рукам, как в случае op_z?

op_z лучше (мне) было бы назвать friend operator+(C&, int)

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

Но стандарт не говорит о том, что объект, которым инициализорована ссылка, должен жить столько, сколько живёт ссылка. Это не задача компилятора. Это задача программиста.

Нет, т.к. объект, на который указывает ссылка, убивает не программист, а *компилятор* (а именно, временный объект в выражении).

Т.е. вот в таком коде очевидно виноват программист, т.к. *он* вызывает delete:

int* ip = new int(1);
delete ip;
const int& ir = *ip;

а вот в таком коде очевидно виноват компилятор, т.к. *он* вызывает delete или что у него там:

C c1(1); 
C c2(2); 
const C& c4 = (c1+c2)+1;

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

>> Язык такой. Так будет работать:

За исключением того, что у тебя будет c5.i==6, а в моем варианте было c5.i==5 :-)

Точно. Не заметил, что выражение c2 + 1 изменит c2.i. Это так и должно быть?

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

>>> Язык такой. Так будет работать:

За исключением того, что у тебя будет c5.i==6, а в моем варианте было c5.i==5 :-)

Точно. Не заметил, что выражение c2 + 1 изменит c2.i. Это так и должно быть?

Хотя в принципе не важно. Просто хотел в коде показать, почему после const C& c3 = (c1+c2); не уничтожается объект, а после const C& c4 = (c1+c2)+1; уничтожается. Тогда const C& c4 = С((c1+c2)+1);

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

в коде const C& c4 = (c1+c2)+1; компилятор не виноват. Ссылка инициализируется ссылкой на объект, а не объектом. Поэтому временный объект честно удаляется в конце выражения, так как он не инициализировал ссылку.

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

В обоих случаях ссылки инициализируются ссылками. Временные объекты сами по себе, так как ими ничего не инициализируется.

const C& c10= c1+3;
const C& c11 = c10;

anonymous
()

Что происходит! Сравните 2 варианта!

Казалось бы, определяя BAD, мы делаем вместо френда метод, но в результате ссылка становится мертвой.

И еще, friend operator + ведет себя не так, как op_z в прошом примере, хотя его код не поменялся.

#include <iostream>
using namespace std;

class C
{
public:
  C(int i): i(i), s("live") { cout << "ctor: " << i << endl; }
  C operator+(const C& c)  { cout << "plus: " << i << "+" << c.i << endl; return C(i+c.i); }
#ifdef BAD
  C& operator+(int ii) {
    cout << "op_x: " << i << "+" << ii << endl; i += ii; return *this; 
  }
#else
  friend C& operator + (C& that, int ii) {
    cout << "op_z: " << that.i << "+" << ii << endl;
    that.i += ii;
    return that;
  }
#endif
  void info() const { cout << "info: " << i << " " << s << endl; }
  ~C() { s="dead"; cout << "dtor: " << i << endl; }
private:
  int i;
  const char* s;
};
int main()
{
  C c1(1);
  C c2(2);
  const C& c4 = (c1+c2)+1;
  c1.info();
  c2.info();
  c4.info();
  return 0;
}
$ g++ -Wall c.cxx
$ ./a.out
ctor: 1
ctor: 2
ctor: 1
plus: 1+2
ctor: 3
plus: 3+1
ctor: 4
dtor: 3
dtor: 1
info: 1 live
info: 2 live
info: 4 live
dtor: 4
dtor: 2
dtor: 1
$ g++ -DBAD -Wall c.cxx
$ ./a.out
ctor: 1
ctor: 2
plus: 1+2
ctor: 3
op_x: 3+1
dtor: 4
info: 1 live
info: 2 live
info: 4 dead
dtor: 2
dtor: 1
$
www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от nozh

> Точно. Не заметил, что выражение c2 + 1 изменит c2.i. Это так и должно быть?

С одной стороны, С& C::operator +(int) жутко кривой, с другой — вроде имеет право на жизнь.

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

> И еще, friend operator + ведет себя не так, как op_z в прошом примере, хотя его код не поменялся.

Он у тебя вообще не вызывается.

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

Что же тут удивительного?
В одном случае создаётсмя временный объект и им инициализируется ссылка:
C operator+(const C& c).
В другом случае ссылка инициализируется ссылкой:
C& operator+(int ii). Объект уничтожается в конце выражения.

Всё законно. И объяснено много раз.

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

> Всё законно. И объяснено много раз.

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

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

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

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

В данных примерах ссылка инициализируется 2 способами. Объектом С и ссылкой С& на какой-то неизвесный объект, который возможно к временному объекту не имеет отношения. Перечитайте предыдущий вопрос по этой теме на форуме, там же уже даны ответы.

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

> Вы как будто про сборку мусора пишите. Нет сборки мусора в С++. И объект можно удалить, даже если на него ссылается 100000000 указателей.

А вот не надо путать указатели и *ссылки*. Например, наличие одной константной ссылки на временный объект заставляет компилятор не удалять его, а наличие хоть 10000000000 константных указателей на него — не заставляет.

Кроме то, я уж говорил — если *я* удаляю объект под константной ссылкой, то виноват я, но если это *втихую* делает компилятор — то виноват либо он, либо авторы языка.

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

www_linux_org_ru ★★★★★
() автор топика

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

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

Не ну как можно не понимать возврат объекта и возврат ссылки.

1)
Ссылка инициализируется временным объектом. Тогда объект сохраняется до конца видимости ссылки. Это работает.

2)
Ссылка инициализируется ссылкой, которая возвращается методом временного объекта. Таким образом временный объект всего лишь поставляет ссылку на какой-то объект, не обязательно на себя. Сам временный объект самим собой ничего не инициализирует. И должен значит быть уничтожен в конце выражения. Это работает.


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

Нет никаких сюрпризов. Вы бы по другому не смогли бы сделать в данном случае. Как Вы определите, что C& operator+ возвращает ссылку именно на себя, а не на неизвестно какой объект неизвесно откуда? Никак. Поэтому временный объект должен быть уничтожен, так как он ничего не инициализирует. Ссылка инициализируется ссылкой, возвращаемой функцией C& operator+. Стандарт же говорит про инициализацию ссылки объектом.

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

Рассмотрим оператор класса

C& operator+(int){ /* return *this;*/ return Global_C; }

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

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

>Если это действительно соответствует стандарту, то это баг языка. Т.к. в безбажном языке компилятор *молча* не удалит объект, про который знает, что программист на него ссылается константной ссылкой.
Хи. Ссылка может указывать на динамический объект или на объект в другой области видимости. Ссылка не есть защита от удаления объекта. По поводу бажности это как-то вообще не в тему.

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

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

я понял о чем ты, однако в случае:

const C& c7 = op_z(c1+c2, 4);

компилятор соображает и выдает ошибку.

почему он не может выдавать ее и в случае

const C& c4 = (c1+c2)+1;

а?

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

> Ссылка не есть защита от удаления объекта.

Давай поточнее.

*Константная* ссылка (в некоторых случаях) есть защита от удаления *временного* объекта *компилятором*.

Я про это говорю.

www_linux_org_ru ★★★★★
() автор топика

В С++ есть правила для времени жизни временных объектов. Конечно можно по-разному к ним относится, но нужно придерживаться их. Временные объекты уничтожаются в «конце полного выражения». Где полное выражение это выражение не являющееся подвыражением другого. Ссылка или указатель ссылается на временный объект это не имеет значение. В общем с3, с4, с5 использовать после строк, где они объявляются нельзя, так как строки их объявления являются полными выражениями для временных объектов.

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

А тут тоже компилятор виноват:

C c1(1);  
C c2(2);  
const C *c4 = &((c1+c2) + 1);
?

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

>*Константная* ссылка (в некоторых случаях) есть защита от удаления *временного* объекта *компилятором*.
Огласите эти случаи.

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

>Временные объекты уничтожаются в «конце полного выражения». Исключение: Временный объект инициализирует собой константную ссылку. Тогда он живёт вместе со ссылкой.

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

Стандарт 12.2 Temporary objects

С& с = С();

временный объект С() будет жить пока живёт ссылка с.

Хм, действительно. В 12.2.5 даже пример есть:

class C {
        // ...
public:
        C();
        C(int);
        friend C operator+(const C&, const C&);
        ~C();
};
C obj1;
const C& cr = C(16)+C(23);
C obj2;
/*
the expression C(16)+C(23) creates three temporaries. A first temporary T1 to hold the result of the
expression C(16), a second temporary T2 to hold the result of the expression C(23), and a third tempo-
rary T3 to hold the result of the addition of these two expressions. The temporary T3 is then bound to the
reference cr. It is unspecified whether T1 or T2 is created first. On an implementation where T1 is cre-
ated before T2, it is guaranteed that T2 is destroyed before T1. The temporaries T1 and T2 are bound to
the reference parameters of operator+; these temporaries are destroyed at the end of the full expression
containing the call to operator+. The temporary T3 bound to the reference cr is destroyed at the end of
cr’s lifetime, that is, at the end of the program. In addition, the order in which T3 is destroyed takes into
account the destruction order of other objects with static storage duration. That is, because obj1 is con-
structed before T3, and T3 is constructed before obj2, it is guaranteed that obj2 is destroyed before T3,
and that T3 is destroyed before obj1.
*/
Похоже следует писать багрепорт.

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

В примере в стандарте оператор возвращает объект а не ссылку.

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

В примере в стандарте возвращается объект, а не ссылка. А в примерах, на которые тут ругаются, возвращается ссылка. Ошибки нет в GCC.

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

Тут объектом инициализируется ссылка

const C& cr = C(16)+C(23);

Тут ссылка инициализируется ссылкой неизвестно на что

C& f();
class C
{
public:
  C& f();
  C& operator+(int);
}
// всё нижеследущее эквивалентно, все временные объекты уничтожатся
const C& cr1 = f(); 
const C& cr2 = C().f(); 
const C& cr3 = C() + 1; 
const C& cr4 = C().operator+(1); 

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

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

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

Аааааа! Кажется допёрло. У автора же operator+() и op_y() возвращает *this, который удаляется...

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

/tmp :$icpc -Wall -Wcheck temp.cpp
temp.cpp(30): remark #383: value copied to temporary, reference to temporary used
const C& c4 = (c1+c2)+1;
^

temp.cpp(30): remark #981: operands are evaluated in unspecified order
const C& c4 = (c1+c2)+1;
^

/tmp :$icpc -Wall -Wcheck temp.cpp -o one
temp.cpp(30): remark #383: value copied to temporary, reference to temporary used
const C& c4 = (c1+c2)+1;
^

temp.cpp(30): remark #981: operands are evaluated in unspecified order
const C& c4 = (c1+c2)+1;
^

/tmp :$icpc -DBAD -Wall -Wcheck temp.cpp -o two
/tmp :$./one
ctor: 1
ctor: 2
plus: 1+2
ctor: 3
ctor: 1
plus: 3+1
ctor: 4
dtor: 1
dtor: 3
info: 1 live
info: 2 live
info: 4 live
dtor: 4
dtor: 2
dtor: 1
/tmp :$./two
ctor: 1
ctor: 2
plus: 1+2
ctor: 3
op_x: 3+1
dtor: 4
info: 1 live
info: 2 live
info: 4 dead
dtor: 2
dtor: 1
/tmp :$

Sylvia ★★★★★
()

> C(int i): i(i), s(«live»)

Я бы такого не написал ;-)

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

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

> // всё нижеследущее эквивалентно, все временные объекты уничтожатся

const C& cr1 = f();

const C& cr2 = C().f();


const C& cr3 = C() + 1;


const C& cr4 = C().operator+(1);



это понятно, но ты пока не ответил — почему же *тогда* компилятор все же ловит ошибку в const C& c7 = op_z(c1+c2, 4); ?

________________________

мое предварительное мнение — язык должен запрещать использовать временные объекты в качестве this в том случае, если использование идет в виде this->f(x,y,z) и f не объявлена как const

с++ уже запрещает аналогичное в случае, когда юзается g(temporary,x,y,z) и тип первого аргумента g — неконстантная ссылка

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

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

одобряю этот коммент

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

> Странно у вас + как += работает. Не должно быть такого

не должно

так что где-то компилятор дожен бить за такое по рукам

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

>так что где-то компилятор дожен бить за такое по рукам

За что? За возврат функцией ссылки? За инициализацию ссылки ссылкой?

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

>язык должен запрещать

В С++ одно и тоже можно сделать множеством способов. А то, что при этом можно понаписать неизвесно что, так это уже вопрос у программисту. Объезянам нельзя давать гранату. Так и на С++ можно писать абсолютно непонятные программы. Иначе есть Java и т.п., её и придумали для избавления от сложных сторон С++.

anonymous
()

рассмотрим вот такой код:

#include <iostream>
using namespace std;

class C
{
public:
  explicit C(int i): i(i), s("live") { cout << "ctor: " << i << endl; }
#ifdef INNOCENT_CHANGE
  C& operator+(double x) {
    cout << "meth: " << i << "+" << (int)x << endl; i += (int)x; return *this; 
  }
#else
  friend C& operator + (C& that, double x) {
    cout << "func: " << that.i << "+" << (int)x << endl;
    that.i += (int)x;
    return that;
  }
#endif
  friend C operator + (double x, C& that) {
    cout << "d+C : " << that.i << "+" << (int)x << endl;
    return C(that.i+(int)x);
  }
  void info() const { cout << "info: " << i << " " << s << endl; }
  ~C() { s="dead"; cout << "dtor: " << i << endl; }
private:
  int i;
  const char* s;
};
int main()
{
  C c1(1);
  const C& c2 = (3.1+c1)+4.2;
  c1.info();
  c2.info();
  return 0;
}
$ g++ -Wall e.cxx
e.cxx: In function ‘int main()’:
e.cxx:32: error: no match for ‘operator+’ in ‘operator+(double, C&)(((C&)(& c1))) + 4.20000000000000017763568394002504646778106689453e+0’
e.cxx:19: note: candidates are: C operator+(double, C&)
e.cxx:13: note:                 C& operator+(C&, double)
$ g++ -DINNOCENT_CHANGE -Wall e.cxx
$ ./a.out
ctor: 1
d+C : 1+3
ctor: 4
meth: 4+4
dtor: 8
info: 1 live
info: 8 dead
dtor: 1
$

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

во втором случае ( -DINNOCENT_CHANGE ) делаем ну совершенно несущественное изменение — переделывает друга в метод, и вуаля — комплятор заткнулся, а в рантайме имеем ссылку на мертвый объект

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