Toon posts:

[c++] arguments by-reference

Pagina: 1
Acties:
  • 248 views sinds 30-01-2008
  • Reageer

Verwijderd

Topicstarter
Hoi,

Ik ben iets raars tegengekomen met GCC. Als ik de volgende code compileer:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

class bar {
        public:
        bar() { a = 5; }
        bar(int b) { a = b;}
        ~bar() {}
        int a;
};

void foo(bar &b)
{
        cout << b.a <<endl;
}

int main(int argc, char* argv[])
{
        foo(bar(4));
        return 0;
}


.. dan geeft GCC als resultaat:

code:
1
2
3
4
5
[wouter@ttyp3] fear:~$ g++ blah.cc -o blah
blah.cc: In function `int main(int, char **)':
blah.cc:19: initialization of non-const reference type `class bar &'
blah.cc:19: from rvalue of type `bar'
blah.cc:13: in passing argument 1 of `foo(bar &)'


Als ik regel 19 wijzig in:

C++:
19
        foo(bar b(4));


.. dan geeft GCC:

code:
1
2
3
[wouter@ttyp3] fear:~$ g++ blah.cc -o blah
blah.cc: In function `int main(int, char **)':
blah.cc:19: syntax error before `('


Als ik vervolgens van regel 19 het volgende maak:

C++:
17
18
19
20
int main(int argc, char* argv[])
{
        bar b(4);
        foo(b);


.. dan pakt gcc 'em wel. En als ik dat weer terug zet en op regel 12 de functie-declaratie van foo() aanpas:

C++:
12
void foo(const bar &b)


.. dan pakt GCC wonder boven wonder beide bovenstaande pogingen met temporaries.

Ik heb dit met zowel GCC 2.95.3 (OpenBSD 3.2 native) als GCC 3.2 (uit de OpenBSD 3.2 ports tree) geprobeerd, beiden weigeren.

Visual C++ .NET (7.0) lijkt het wel goed te doen; dat compileerd in ieder geval, en de bovenstaande test draait ook goed.

Kan iemand mij misschien het waarom hierachter uitleggen, want ik begrijp er geen hout van.. een variabele is toch een variabele? En dat een temporary niet bij by-reference zou kunnen werken kan ik me iets bij voorstellen, maar waarom werkt het dan wel bij een const reference?

Bij voorbaat dank,
Tabsels.

[ Voor 9% gewijzigd door Verwijderd op 01-01-2003 23:53 ]


Verwijderd

VC6 compileerd 'm ook zonder nukken dus 't zal wel 'n gcc specific iets zijn vermoed ik.

Verwijderd

In mijn topic van een paar maanden geleden:
[rml][ c++] Nonconst references initializeren met temporaries[/rml]
is dit uitgebreid besproken.

[ Voor 14% gewijzigd door Verwijderd op 02-01-2003 00:40 ]


  • Daggie
  • Registratie: Juni 2002
  • Laatst online: 13-08-2025
Borland 5.02 doet het ook ...

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Het is een fout van vc6 dat die het wel doet, niet een fout van gcc dat die het niet doet. De C++ standaard verbied het namelijk.

Het is trouwens altijd een goed idee om even Comeau te proberen. Dat kan hier online: http://www.comeaucomputing.com/tryitout/
Thank you for testing your code with Comeau C/C++!
Tell others about http://www.comeaucomputing.com/tryitout !

Your Comeau C/C++ test results are as follows:

Comeau C/C++ 4.3.0.1 (Aug 20 2002 15:39:28) for ONLINE_EVALUATION
Copyright 1988-2002 Comeau Computing. All rights reserved.
MODE:strict errors C++

"ComeauTest.c", line 19: error: initial value of reference to non-const must be an
lvalue
foo(bar(4));
^

1 error detected in the compilation of "ComeauTest.c".

In strict mode, with -tused, Compile failed
Hit the Back Button to review your code and compile options.
Comeau C/C++ 4.3.0.1 is now available! Only $50 for most platforms!!
Check our support for new C compiler backends for Windows.
Buy Comeau C++ Today ... Anything Less Is A-

[ Voor 79% gewijzigd door Zoijar op 02-01-2003 10:56 ]


Verwijderd

Je hoeft in principe alleen maar dit te veranderen in jouw code:
C++:
12
void foo(const bar &b)
Want je verandert verder niets aan het object (en dat had ook niet gemogen, zoals gcc al aangeeft).

Het is trouwens vrij ongebruikelijk in C++ om datamembers public te maken, gebruikelijker is het om deze via interfaces te benaderen. Het is ook gebruikelijker om bij de constructor de datamembers met de opgegeven waarde te initialiseren i.p.v. te assignen. Ook is het misschien netter om deze "foo" als interface van de class te declareren. Maar dat is natuurlijk grotendeels persoonlijke voorkeur...
.
Hoe ik het zelf eerder zou doen (met drie mogelijke benaderingen voor foo(...)):
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>

class bar {
private:
        int a;
public:
        bar(int b = 5) : a(b) {};
        ~bar() {};
        int getA() const { return a; };
        void setA(int b) { a = b; };
        void foo() { std::cout << a << std::endl; };
};

void foo(const bar& b)
{
        std::cout << b.getA() << std::endl;
}

void foo(int b)
{
        std::cout << b << std::endl;
}

int main(int argc, char* argv[])
{
        bar(4).foo();
        foo(bar(4));
        foo(bar(4).getA());
// of nog beter/sneller/enz.: ;)
        foo(4);
        return 0;
}

[ Voor 18% gewijzigd door Verwijderd op 02-01-2003 13:03 ]


Verwijderd

Zoijar schreef op 02 januari 2003 @ 10:51:
Het is een fout van vc6 dat die het wel doet, niet een fout van gcc dat die het niet doet.
Ja en nee. Ja, het is terecht dat gcc een error geeft aangezien wat hier staat niet mag en de C++ standaard verbiedt datgene wat er letterlijk staat.

Echter VC++ en Borland C++ e.d hebben in zekere zin ook gelijk aangezien de functie niks verandert aan het object en dus het argument -impliciet- als const gebruikt, en dit dus eigenlijk helemaal niet gaat om een non-const. Deze compilers kijken hier wat verder dan hun neus lang is, het is de vraag of dit wenselijk is zolang het niet schaadt, maar je kunt het ook uitschakelen :D (Vgl. met de vraag of je door rood mag rijden als er verder niemand het kruispunt nadert...)

Zodra de functie het object wel van toestand wil gaan veranderen, zullen ook deze compilers een error genereren... Maar een warning vind ik wel op z'n plaats...

[ Voor 3% gewijzigd door Verwijderd op 02-01-2003 13:29 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Imho moet een compiler niets aannemen, maar alles letterlijk nemen zoals het er staat. Dit is juist het gedrag dat zorgt voor niet portable code en verassingen achteraf. Niet eens tussen verschillende platformen/compilers, maar zelfs ten opzichte van toekomstige versies van dezelfde compiler.

Verwijderd

Zoijar schreef op 02 januari 2003 @ 14:46:
Imho moet een compiler niets aannemen, maar alles letterlijk nemen zoals het er staat. Dit is juist het gedrag dat zorgt voor niet portable code en verassingen achteraf. Niet eens tussen verschillende platformen/compilers, maar zelfs ten opzichte van toekomstige versies van dezelfde compiler.
Maar dit is niet een voorbeeld van aannemen, maar van meten en daaruitvolgend weten...
Die intelligentie mag een compiler van mij juist wel hebben, mits dit uitschakelbaar is en een waarschuwing zou ik absoluut verplichten in zo'n geval als dit. En in stricte ansi- of iso-modus zou ik persoonlijk pas een error gaan genereren.
Dit schaadt op geen enkele wijze de portabiliteit zolang hetzelfde ondersteund wordt wat andere compilers kunnen. Door const gedrag te detecteren waar dat niet als zodanig is gedefineerd, kan een compiler de te genereren code simpeler en sneller maken. En het enige waar (de meeste) klanten naar kijken bij de keuze voor een compiler is de gegenereerde code...

En hoe defineer je portabiliteit? Je hoeft immers const niet weg te laten, het mag... Zodra je in je code afwijkt van de standaard, bevind je je op glad ijs, maar ook de standaard zelf geeft niet altijd een resoluut houvast. Neem de bitsize van een char of een int, of een right-shift op een signed waarde, enz.

/me Ikzelf zou overigens wel gewoon altijd met "const" gebruiken en de error altijd aan hebben staan (maar liever als warning).
offtopic:
Maar niemand zou iets anders durven te verwachten dan dat jij hierin heel strict zou zijn ;)
Maar wat ik me wel afvraag: Hou jij je nu ook zo strict aan verkeersregels? :X

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 21-08 17:14
Voor alle duidelijkheid: Dat VC6 het beschreven gedrag vertoont is niet het gevolg van verder kijken. Dat kan ook niet, want in main() heb je meestal alleen foo.h, en niet foo.cpp bij de hand. Je weet dus alleen wat de declaratie van foo is. Dat in foo.cpp vervolgens die non-const reference niet gewijzigd wordt, dat kun je niet uit foo.h halen.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Verwijderd

MSalters schreef op 02 January 2003 @ 17:10:
Voor alle duidelijkheid: Dat VC6 het beschreven gedrag vertoont is niet het gevolg van verder kijken. Dat kan ook niet, want in main() heb je meestal alleen foo.h, en niet foo.cpp bij de hand. Je weet dus alleen wat de declaratie van foo is. Dat in foo.cpp vervolgens die non-const reference niet gewijzigd wordt, dat kun je niet uit foo.h halen.
De meeste optimalisaties kunnen zelfs alleen indien de implementatie al bekend is, vgl maar met het inline-en van functies, kan alleen in compile-time, niet in link-time. Ik denk dat het nogal rechtlijnig is om te stellen dat dit "niet het gevolg is van", meerdere factoren kunnen een rol spelen, tenzij jij toegang hebt tot de sourcecode van VC++ :? , dan trek ik alles in w.b.t. VC++...

Waar dit gedrag ook door veroorzaakt wordt, als VC++ dit doet terwijl de implementatie van foo(bar&) niet bekend is, dan is dat behoorlijk ernstig. Of deze staat een temp. object toe als non-const rvalue? Dat negeert deze direct de regels (i.p.v. indirect door reference naar const te promoten, wat ik meer zie als het uitbreiden van de regels ;) ).

En als de implementatie bekend is mag de compiler zelfs het object wegoptimaliseren en direct de integer 4 naar std::cout sturen.

En ik weet vrij zeker van een aantal C++ compilers dat deze met een redelijk optimalisatie-level niet-const functies toch checken op het al dan niet veranderen van het object. Als dat niet het geval is hoeft niet het object weer gesync'd te worden met de waarden in de functie, dat scheelt weer wat indirect addressed IO. Het is dan slechts nog een kleine stap om een non-const reference te promoten naar een const reference. Dit zou geen nare bijverschijnselen moeten/mogen geven (maar zou de gebruiker er zeker op geattendeerd mogen worden dat zijn code (op zijn minst) beter kan en dat de compiler iets doet wat niet perse tot zijn takenpakket hoort). Maar die keuze tot promoten maken volgens mij slechts een paar compilers. Dit kan dus alleen als de implementatie geinclude is. En de compilers waarvan ik vrij zeker denk te weten dat zij dit kunnen, zijn trouwens wel DSP compilers (dus specialistisch). Overigens is dit dan nog heel beknopt beschreven, want er komen wel wat meer optimalisatieslagen aan te pas.

[ Voor 9% gewijzigd door Verwijderd op 02-01-2003 18:27 ]


  • AaroN
  • Registratie: Februari 2001
  • Laatst online: 16-08-2023

AaroN

JayGTeam (213177)

Op pagina 255 midden van "The C++ Programming Language Special Edition" van Stroustrup staat: 'Remeber tha 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).'

Hier staat dus duidelijk dat GCC gelijk heeft. Het komt vaker voor dat MS VC++ een objectje cast om 'gebruiksvriendelijker' te zijn.. Maar het is dus geen officiële C++ syntax. ;)

[ Voor 18% gewijzigd door AaroN op 02-01-2003 22:30 ]

JayGTeam (213177)


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-08 13:19

.oisyn

Moderator Devschuur®

Demotivational Speaker

AaroN schreef op 02 January 2003 @ 22:29:
Het komt vaker voor dat MS VC++ een objectje cast om 'gebruiksvriendelijker' te zijn.. Maar het is dus geen officiële C++ syntax. ;)


VC6 en lager lappen idd alle standaarden aan hun laars, maar sinds 7.0 is dit gigantisch verbeterd, en 7.1 is zelfs bijna helemaal ANSI compliant, met uitzondering van een paar features

Wat ook veel mensen vergeten is de compileroptie om language extensions uit te zetten, een optie die gcc ook heeft (en ja, ook gcc heeft zat extensions die niet aan de standaard voldoen en toch default aan staan). Nadat de extensions zijn uitgezet report VC 7.1 gewoon diezelfde error

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Ik snap echt niet waarom C++ compilers language extensions hebben. Het voegt niets toe, het zorgt alleen maar voor problemen. Als er een bug in je source zit moet jij die maken, niet de compiler. Moderne parsers kunnen ook heel goed error correcten, ze weten meestal wel wat je waarschijnlijk bedoeld maar geven toch een fout voor het geval dat. Denk aan een vergeten ";". Maarja...

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-08 13:19

.oisyn

Moderator Devschuur®

Demotivational Speaker

Het voegt niets toe? Wat dacht je van COM features, DLL features, features om te zorgen dat bepaalde variabelen/functies in andere segmenten terecht komen, property support, enzovoort... Dat zijn allemaal hele platform specifieke dingen die je het leven als programmeur een stuk makkelijker maken. Je moet het dan ook niet zien als 'error-correction'

Of natuurlijk de backwards compatibility, wat idd op zich vervelend is, omdat je dan nooit van die foute dingen afkomt, maar wel handig is om oude code mee te compilen.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 21-08 17:14
Verwijderd schreef op 02 januari 2003 @ 18:14:
[...]

De meeste optimalisaties kunnen zelfs alleen indien de implementatie al bekend is, vgl maar met het inline-en van functies, kan alleen in compile-time, niet in link-time. Ik denk dat het nogal rechtlijnig is om te stellen dat dit "niet het gevolg is van", meerdere factoren kunnen een rol spelen, tenzij jij toegang hebt tot de sourcecode van VC++ :? , dan trek ik alles in w.b.t. VC++...
Je kiest wel weer een slecht voorbeeld; VC7 zou functies moeten kunnen inlinen tijdens het linken.
Maar dat het bovenstaande gedrag optreed kun je zien door functioneel gedrag. Je kunt nl. een main.obj compileren, en vervolgens pas een foo.cpp schrijven die door main.obj gebruikt wordt.
En ik weet vrij zeker van een aantal C++ compilers dat deze met een redelijk optimalisatie-level niet-const functies toch checken op het al dan niet veranderen van het object. Als dat niet het geval is hoeft niet het object weer gesync'd te worden met de waarden in de functie, dat scheelt weer wat indirect addressed IO. Het is dan slechts nog een kleine stap om een non-const reference te promoten naar een const reference. Dit zou geen nare bijverschijnselen moeten/mogen geven (maar zou de gebruiker er zeker op geattendeerd mogen worden dat zijn code (op zijn minst) beter kan en dat de compiler iets doet wat niet perse tot zijn takenpakket hoort).
Nou nee, het werkt iets anders. const/non-const beinvloed overloads e.d. en kan dus heel andere functies tot gevolg hebben. Bovendien is er nog zoiets als mutable. Daarom negeren optimizers attributen als const en werken ze met daadwerkelijke writes.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Verwijderd

MSalters schreef op 03 January 2003 @ 09:57:
Je kiest wel weer een slecht voorbeeld; VC7 zou functies moeten kunnen inlinen tijdens het linken.
Als VC7 nog kan inlinen noem ik die fase geen "linken"... Bij het linken worden objecten samengevoegd en worden external references ingevuld door globale waarden en offsets. Het lijkt mij nogal onwaarschijnlijk dat VC nog gaat inline-en tijdens het linken, want dan veranderen alle offsets opnieuw en is er de grote kans dat de (lokale) short jumps niet meer werken...onmogelijk lijkt mij zo...
Wel is het mogelijk om door de compiler gegenereerde intermediate language files vanuit verschillende sourcefiles te combineren en dit doet VC7 waarschijnlijk, maar deze IL is een tussenfase van de compiler en dit is dan dus in compile-time mijns insziens...
offtopic:
Trouwens, .NET is waarschijnlijk ook gewoon gebaseerd op de oorspronkelijke IL van VC, denk ik :?
Maar dat het bovenstaande gedrag optreed kun je zien door functioneel gedrag. Je kunt nl. een main.obj compileren, en vervolgens pas een foo.cpp schrijven die door main.obj gebruikt wordt.
Inline-en kan alleen gebeuren als de inline-implementatie bekend is, ook van inline-functies wordt er een niet geinline-de versie van de functie gecompileerd. Die wordt eventueel later weggegooid samen met andere functies die in een target-applicatie niet gebruik worden. In jouw voorbeeld wordt deze functie gebruikt voor main.obj
Nou nee, het werkt iets anders. const/non-const beinvloed overloads e.d. en kan dus heel andere functies tot gevolg hebben.
const/non-const beinvloedt de mangled name van de functie, toch hopelijk niet de calling-convention voor een functie? Of is dat wel zo voor de x86 :?

Daarvoor kan men het gemanglede functiesymbool met het naar const-reference gepromote argument als een weak symbol genereren, toch? Dan zie ik nog even geen problemen, wordt er vervolgens ook een const-reference variant van de functie meelinked, dan zal deze altijd het weak symbol overrulen... Dan zie ik verder nu nog zo 1..2..3 geen problemen met overloads, plz explain als jij die wel al ziet...
Bovendien is er nog zoiets als mutable. Daarom negeren optimizers attributen als const en werken ze met daadwerkelijke writes.
Kun je me dit wat beter uitleggen, hoe je dit in verband houdt met het voorgaande, want waarschijnlijk lees ik dit verkeerd |:(

[ Voor 50% gewijzigd door Verwijderd op 03-01-2003 11:01 ]


  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Waarom mag een niet-const reference naar een temporary eigenlijk niet?
Ok, het veranderen heeft niet zoveel nut, maar het kan toch wel?

  • Eelis
  • Registratie: Januari 2003
  • Laatst online: 21-02-2015
.

[ Voor 100% gewijzigd door Eelis op 18-02-2015 19:48 ]


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 21-08 17:14
Verwijderd schreef op 03 januari 2003 @ 10:16:
const/non-const beinvloedt de mangled name van de functie, toch hopelijk niet de calling-convention voor een functie? Of is dat wel zo voor de x86 :?
Dat was een reactie op jouw eerdere opmerking:
Het is dan slechts nog een kleine stap om een non-const reference te promoten naar een const reference.
Dat gaat dus heel erg fout, als foo( int& ) vervolgens bar( int& ) aanroept, en bar heeft ook een bar ( const int& ) overload. Sowieso kan de compiler die "promotie" niet doen @ compile time; op dat moment kun je nog in een andere .cpp de foo( int const& ) toevoegen. En link-time die promotie doen is onmogelijk, vanwege de bar( int&) situatie (die zou inlined kunnen zijn).
Daarvoor kan men het gemanglede functiesymbool met het naar const-reference gepromote argument als een weak symbol genereren, toch? Dan zie ik nog even geen problemen, wordt er vervolgens ook een const-reference variant van de functie meelinked, dan zal deze altijd het weak symbol overrulen... Dan zie ik verder nu nog zo 1..2..3 geen problemen met overloads, plz explain als jij die wel al ziet...
(
Dan moet je ook nog eens opletten dat je die gegenereerde weak variant niet per ongeluk inlined, dat die niet meedoet met overload resolution, dat je nergens per ongeluk het adres er van neemt, en nog wat subtiliteiten die ik nu ongetwijfeld vergeet. Het wordt op die manier wel erg duur, alleen om de luie programmeur 5 toetsaanslagen te besparen.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Verwijderd

MSalters schreef op 03 January 2003 @ 13:09:
Dat gaat dus heel erg fout, als foo( int& ) vervolgens bar( int& ) aanroept, en bar heeft ook een bar ( const int& ) overload. Sowieso kan de compiler die "promotie" niet doen @ compile time; op dat moment kun je nog in een andere .cpp de foo( int const& ) toevoegen. En link-time die promotie doen is onmogelijk, vanwege de bar( int&) situatie (die zou inlined kunnen zijn).
Kan wel, want als die foo(const bar &) bestaat moet de interfacedefinitie geinclude zijn tijdens het compileren, anders wordt die ook niet gekozen (wordt bepaald in compiletime a.d.h.v. de aanwezige interfacedefinities)... En staat die wel in een headerfile, maar is deze niet geimplementeerd dan krijg je een linker error...
Dan moet je ook nog eens opletten dat je die gegenereerde weak variant niet per ongeluk inlined, dat die niet meedoet met overload resolution, dat je nergens per ongeluk het adres er van neemt, en nog wat subtiliteiten die ik nu ongetwijfeld vergeet. Het wordt op die manier wel erg duur, alleen om de luie programmeur 5 toetsaanslagen te besparen.
Dat is juist de enige goede benadering van een compiler om weak-functies nooit te inlinen, aangezien dit weak symbol tot-en-met linken nog geoverride kan worden. Volgens mij is er geen enkele compiler die weak functions inlined, zou goed fout zijn...

[ Voor 15% gewijzigd door Verwijderd op 07-01-2003 10:27 ]

Pagina: 1