Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Wieso klappt hier kein Call-by-Reference?

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: [ 1 ] > 2 <
010
21.08.2009, 12:27 Uhr
ao

(Operator)


OK, wieder was gelernt. Ist ja auch sinnvoll, dass ein Objekt nicht kopiert werden muss, wenn es nur ausgequetscht werden soll.

Aber in dem vorliegenden Fall kann man das nicht wissen, weil die Methode printModell kein const-Attribut hat, d.h. der Compiler muss von lvalue-Verhalten ausgehen und spätestens vor dem Aufruf von printModell eine Kopie herstellen.

Andererseits ist aus semantischer Sicht das Kopieren eines Auto-Objekts unsinnig und muss by-design verhindert werden, zum Beispiel durch Verstecken des Copy-Konstruktors.

Dieser Post wurde am 21.08.2009 um 12:28 Uhr von ao editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
011
21.08.2009, 13:44 Uhr
~David_pb
Gast



Zitat:

Aber in dem vorliegenden Fall kann man das nicht wissen, weil die Methode printModell kein const-Attribut hat, d.h. der Compiler muss von lvalue-Verhalten ausgehen und spätestens vor dem Aufruf von printModell eine Kopie herstellen.


Der vorliegende Fall ist genau so ein Fall wo der Compiler optimieren kann. Die Übergabe per Wert erlaubt ja das Objekt im Funktionsrumpf zu ändern (das Verhalten ist ja auch bekannt von Trivialen Typen, wie int, die ja meistens per Value übergeben werden). Bei einer konstanten Referenz kann das Objekt nicht geändert werden (was semantisch auch sehr Sinnvoll ist). Allerdings gibt es hin und wieder den Fall das übergebene Objekte änderbar sein müssen, bekanntes Beispiel ist der Assignment-Operator Implementiert in Form des Copy & Swap Idioms.

Nehmen wir mal folgenden Code:


C++:
struct foo
{
    // ...

    foo& operator=( const foo& other )
    {
        //other.swap( *this ); // fehler, objekt nicht veränderbar
        foo( other ).swap( *this );
        return *this;
    }

    void swap( foo& a )
    {
        // ...
    }
};

foo get_foo()
{
    return foo();
}

int main()
{
    foo x;
    foo y;
    x = y; // 1
    x = get_foo(); // 2
}


In beiden Fällen (1 und 2) wird bei der Zuweisung von x eine Kopie in operator=() erzeugt. Ändert man die Implementierung etwas ab


C++:
foo& operator=( foo other )
{
    other.swap( *this );
    return *this;
}


kann der Compiler Fall 2 optimieren und das Objekt wird nicht nochmals kopiert. Die semantischen Aspekte sind natürlich wieder ein anderes Paar Schuhe. ;-)
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
012
21.08.2009, 16:07 Uhr
ao

(Operator)



Zitat von ~David_pb:

Zitat:

Aber in dem vorliegenden Fall kann man das nicht wissen, weil die Methode printModell kein const-Attribut hat, d.h. der Compiler muss von lvalue-Verhalten ausgehen und spätestens vor dem Aufruf von printModell eine Kopie herstellen.


Der vorliegende Fall ist genau so ein Fall wo der Compiler optimieren kann. Die Übergabe per Wert erlaubt ja das Objekt im Funktionsrumpf zu ändern ...


Es nützt ihm aber nichts. Wir reden doch von Garage::parkeAuto(), richtig?

Wenn der Compiler hier optimiert und eine Copy-Elimination von Auto a macht, dann gelangt das Original-Auto-Objekt in die parke()-Methode hinein, keine Kopie. parke() ruft dann a.printModell(), was (potentiell) das Objekt verändern kann, weil printModell nicht mit const attributiert ist.

Da der Kopf von parke() aber verspricht, dass das äußere Objekt ("Audi A5") by-value übergeben wird, darf es sich nicht ändern.

Der Compiler muss also (wenn er schon unbedingt den Funktionsaufruf optimieren will) spätestens bei printModell das Auto kopieren, sonst riskiert er, einen Fehler zu machen. Ist also Jacke wie Hose.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
013
21.08.2009, 16:08 Uhr
0xdeadbeef
Gott
(Operator)


Schön und gut, aber was der Compiler optimieren kann, ist für den Sprachstandard eher unerheblich. Relevant ist hier, dass temporäre Objekte nur an nicht-volatile, konstante Referenzen gebunden werden können - nachzulesen (etwas versteckt) im Standard unter 8.5.3 (5). Etwas versteckt deshalb, weil es unter "Ansonsten" steht und auch für ein paar andere Fälle gilt. Zum Beispiel ist

C++:
int const &r = 2;


legaler Code,

C++:
int &r = 2;


aber nicht.
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
014
21.08.2009, 18:29 Uhr
~David_pb
Gast



Zitat:

Es nützt ihm aber nichts. Wir reden doch von Garage::parkeAuto(), richtig?

Wenn der Compiler hier optimiert und eine Copy-Elimination von Auto a macht, dann gelangt das Original-Auto-Objekt in die parke()-Methode hinein, keine Kopie. parke() ruft dann a.printModell(), was (potentiell) das Objekt verändern kann, weil printModell nicht mit const attributiert ist.

Da der Kopf von parke() aber verspricht, dass das äußere Objekt ("Audi A5") by-value übergeben wird, darf es sich nicht ändern.

Der Compiler muss also (wenn er schon unbedingt den Funktionsaufruf optimieren will) spätestens bei printModell das Auto kopieren, sonst riskiert er, einen Fehler zu machen. Ist also Jacke wie Hose.


Du hast das Prinzip anscheinend noch nicht verstanden. Ein rvalue ist an kein benanntes Objekt gebunden, das heißt es existieren keine weiteren Referenzen auf dieses Objekt und genau deswegen darf ja optimiert werden. Der Compiler kann (und wird) in _diesem_ Fall optimieren:


C++:
g.parkeAuto(Auto("Audi A5")); // <- hier kann optimiert werden
Auto foo( "Audi A5" );
g.parkeAuto( foo ); // <- hier nicht




Zitat:

Schön und gut, aber was der Compiler optimieren kann, ist für den Sprachstandard eher unerheblich. Relevant ist hier, dass temporäre Objekte nur an nicht-volatile, konstante Referenzen gebunden werden können - nachzulesen (etwas versteckt) im Standard unter 8.5.3 (5). Etwas versteckt deshalb, weil es unter "Ansonsten" steht und auch für ein paar andere Fälle gilt. Zum Beispiel ist [...]



Das ist zwar alles richtig, aber was hat das mit der aktuellen Diskussion zu tun? Im Moment geht es doch darum, dass pass by value auch für komplexe Objekte nicht immer teuer sein muss, wie viele anscheinend glauben.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
015
21.08.2009, 19:15 Uhr
ao

(Operator)



Zitat von ~David_pb:

Du hast das Prinzip anscheinend noch nicht verstanden. Ein rvalue ist an kein benanntes Objekt gebunden, das heißt es existieren keine weiteren Referenzen auf dieses Objekt und genau deswegen darf ja optimiert werden. Der Compiler kann (und wird) in _diesem_ Fall optimieren:


C++:
g.parkeAuto(Auto("Audi A5")); // <- hier kann optimiert werden
Auto foo( "Audi A5" );
g.parkeAuto( foo ); // <- hier nicht




Moment, das kann aber nicht sein.

Die Copy-Elimination findet statt zur Übersetzungszeit von Garage::parkeAuto. Zu diesem Zeitpunkt ist aber noch gar nicht festgelegt, ob die Methode so oder so verwendet wird.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
016
21.08.2009, 19:22 Uhr
0xdeadbeef
Gott
(Operator)


Inzwischen laufen hier scheinbar drei Diskussionen gleichzeitig. Angefangen hat es mit der, auf die ich mich bezog.

Die zweite (die ao anfing) hing damit zusammen, dass bei by-Value-Übergabe das übergebene Objekt nicht verändert wird (sondern seine Kopie). Die scheint aber inzwischen beendet zu sein.

Erst die dritte (die du angefangen hast) dreht sich um Performance bei by-Value-Übergabe von temporären Objekten, wobei der Compiler in der Tat optimieren kann (12.1 (15) im Standard). Das selbe gilt auch für die Rückgabe komplexer Datentypen, wo es in der Regel von größerem Interesse ist. Ob er es tut, ist allerdings vom Compiler und seinen Einstellungen abhängig.

Übrigens bezieht sich die Regel im Standard, die du da bemühst, ausdrücklich auf temporäre Objekte, nicht auf rvalues allgemein.

@ao: Ganz generell erlaubt der Standard, das Kopieren von by-Value-Parametern wegzuoptimieren, wenn es sich um ein temporäres Objekt handelt. Das temporäre Objekt wird in allen mir bekannten Fällen gleich an der richtigen Stelle auf den Stack gebaut.
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra

Dieser Post wurde am 21.08.2009 um 19:25 Uhr von 0xdeadbeef editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
017
21.08.2009, 19:33 Uhr
~David_pb
Gast


Es geht nich darum wie die Methode verwendet wird sondern das es sicher ist zu optimieren. In der ersten Zeile wird ein rvalue übergeben da ist es egal was damit in der Funktion passiert, das Objekt wird ja ohnehin nicht von zwei Stellen referenziert. Nehmen wir mal das Orginal und geben Infos über die Konstruktion/Destruktion aus:


C++:
class Auto {
private:
  const char* modell;

public:
  Auto(const char* modell) {
      std::cout << "ctor\n";
      this->modell = modell;
  }

  Auto( const Auto& )
  {
      std::cout << "copy ctor\n";
  }

  ~Auto()
  {
      std::cout <<"dtor\n";
  }

  void SetName( const char* value )
  {
      modell = value;
  }

  void printModell() {
    std::cout<<"tut Modell "<<modell<<std::endl;
  }
};

class Garage {
private:
  const char* name;

public:
  Garage(const char* name) {
    this->name = name;
  }

  void parkeAuto(Auto a) {
    std::cout<<"Garage "<<name<<":";
    a.printModell();
    a.SetName( "xyz" ); // bleibt ohne Effekt!
  }
};

int main()
{
    Garage g = Garage("Meine Garage");
    g.parkeAuto(Auto("Audi A5")); // error: no matching function call to 'Garage::parkeAuto(Auto)'
    std::cout << "----------------\n";
    Auto foo( "Audi A5" );
    g.parkeAuto( foo );
    foo.printModell(); // Immer noch Audi A5
    _getch();
}


Im Normalfall sollte die Ausgabe sein wie folgt:

Code:
ctor
Garage Meine Garage:tut Modell Audi A5
dtor
----------------
ctor
copy ctor
Garage Meine Garage:tut Modell Audi A5
dtor
tut Modell Audi A5


Im ersten Fall wird das Auto erzeugt und an die Methode parkeAuto übergeben (keine Kopie). Im Zweiten wird kopiert (lvalue) und die Änderung bleibt ohne Effekt.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
018
21.08.2009, 19:37 Uhr
~David_pb
Gast



Zitat:

Übrigens bezieht sich die Regel im Standard, die du da bemühst, ausdrücklich auf temporäre Objekte, nicht auf rvalues allgemein.


Stimmt... Ungünstig (falsch) ausgedrückt! Danke für die korrektur.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: [ 1 ] > 2 <     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: