verstehe das mit const reference nicht...
-
nehmen wir mal an ich habe eine klasse rect mit konstruktor:
(1) rect::rect(int width, int height);
so nun nehmen wir mal an ich habe eine Funktion foo:
(2) void foo(const rect& r);
wenn ich jetzt so mache:
(3) foo(rect(10,20));
dann klappt es ja wunderbar - ich verstehe aber gar nicht warum man bei (2) const rect& schreiben muß? Wozu da überhaupt const? Und warum geht es nicht ohne? Wie kann ich dieses const wegkriegen?
PS: void foo(rect r) will ich nicht, da hier 2. Objekt erzeugt wird.
und void foo(rect* r) will ich auch nicht - da ich dann rect nicht inline schreiben kann wie bei (3)
-
rect(10, 20);erzeugt ein temporäres Object. Temporäre Objekte sin in C++ immer konstant, sonst wäre zB sowas möglich:
i++++++++++;Das führt aber nr zur Verwirrung, da i in diesem Fall nur einmal inkrementiert würde. Natürlich gibt es noch viele andere kuriose Situation in denen es zu Problem käme.
Die Tatsache, dass alle temporären Objekte konstant sind, lässt nur zwei Übergabe-Möglichkeiten zu: konstante Referenz oder Wertübergabe.
Eine normale Referenz wäre auch semantisch falsch, da man ja dann sagt: Dieser Paramter wird von der Funktion intern verändert. Die Veränderung von temporären Objekten ist aber mehr als fragwürdig und deshalb imho nicht erlaubt.Gruß
Don06
-
TheShadow2000 schrieb:
Wie kann ich dieses const wegkriegen?
warum willst du es weg? willst du das rect ändern?
-
ja ich verstehe dass es ein temporäres objekt ist... das ist ok... warum aber sollen die funktionen so ein objekt nicht ändern dürfen?
was ist, wenn foo feststellt, dass z.B. rect über der Grenze liegt und dann einen parameter korrigiert, etwa:
class rect { public: int width; int height; rect(int width, int height) { this->width=width; this->height=height; } }; void foo(rect& r) { if (r.width>100) r.width=100; if (r.height>100) r.height=100; //.. } int main() { foo(rect(10,20)); //<<<FEHLER return 0; }
-
Temporäre Objekte sin in C++ immer konstantah langsam verstehe ich wohl...
int a = 1;
//1 ist logisch eine numerische Konstante - es gibt überhaupt keine Möglichkeit oder Sinn 1 direkt zu ändern.rect r = rect(10,20);
//rect(10,20) ist ein konstantes objekt das nach r kopiert wird... rect(10,20) selbst kann man nicht ändern...
-
Bei einer Wertübergabe dürfte der Compiler das temporäre Objekt einspaaren, versuchs doch mal so

EDIT://
btw. wenn du auf die Idee kommst das ganze zu const_casten bekommst du AFAIR undefiniertes Verhalten
-
Wenn du einen Parameter "korrigieren" willst musst du schon selbst eine Kopie anlegen:
void foo(const rect& r) { rect myRect = r; myRect.width = 100; //... }
-
Bei einer Wertübergabe dürfte der Compiler das temporäre Objekt einspaaren, versuchs doch mal so
du meinst so:
void foo(rect r) {}
foo(rect(10,20));
ich denke da wird kopiert - es müsste 1 Objekte im Speicher entstehen... rect(10,20) wäre eine konstante das sowieso im programm ist.
Bei einer Referenz würde kein Objekt entstehen, sondern man hätte die Konstante rect(10,20) gehabt.
-
TheShadow2000 schrieb:
rect r = rect(10,20);
//rect(10,20) ist ein konstantes objekt das nach r kopiert wird... rect(10,20) selbst kann man nicht ändern...Genau, und wenn Du das Teil als Wert übergibst, statt als (konstante) Referenz, passiert dank der Intelligenz moderner Compiler (hoffentlich) das gleiche - das Objekt, auf das Du in der Funktion zugreifst, wird direkt aus den Koordinaten initialisiert. Dann kannst Du es in der Funktion nach belieben korrigieren, benutzen und dann wegwerfen (lassen). Voraussetzung ist natürlich, dass die Klasse die üblichen Kriterien für die Kopierbarkeit erfüllt.
Und wieder einmal gilt: Bitte keine Kopierphobie entwickeln. :xmas1:
-
TheShadow2000 schrieb:
class rect { public: int width; int height; rect(int width, int height) { this->width=width; this->height=height; } }; void foo(rect& r) { if (r.width>100) r.width=100; if (r.height>100) r.height=100; //.. } int main() { foo(rect(10,20)); //<<<FEHLER return 0; }Das ist doch schon sehr nah dran, das einzige Prolem ist wie schon erwähnt das temporäre Objekt. Wenn die Funktion was überprüfen soll, braucht du das Objekt doch sowieso außerhalb wieder, also schreibt man doch einfach:
int main() { rect myrect; foo(myrect); return 0; }Oder worum geht es hier?
-
class rect { typedef unsigned int pos_t; typedef std::pair <pos_t, pos_t> point_t; protected: point_t m_topleft; point_t m_bottomright; public: rect(pos_t x, pos_t y, std::size_t cx, std::size_t cy) : m_topleft(std::make_pair(x, y)), m_bottomright(std::make_pair(x + cx, y + cy)) {} rect(point_t const& topleft, point_t const& bottomright) : m_topleft(topleft), m_bottomright(bottomright) {} public: std::size_t get_width() const { return m_bottomright.first - m_topleft.first; } std::size_t get_height() const { return m_bottomright.second - m_topleft.second; } point_t get_x const { return m_topleft.first; } point_t get_y const { return m_topleft.second; } };
so sieht die Klasse doch net aus 
void foo(rect const& rc) { const std::size_t rc_width(std::min<std::size_t>(rc.get_width(), 100)); const std::size_t rc_height(std::min<std::size_t>(rc.get_height(), 100)); // ... }int main() { foo(rect(0, 0, 10, 20)); }fertig!

-
was ist point_t und std::pair? welche includes braucht man da?
-
xBlackKnightx schrieb:
was ist point_t
(D)Evil schrieb:
typedef unsigned int pos_t; typedef std::pair <pos_t, pos_t> point_t;und std::pair? welche includes braucht man da?
#include <utility>
-
TheShadow2000 schrieb:
ja ich verstehe dass es ein temporäres objekt ist... das ist ok... warum aber sollen die funktionen so ein objekt nicht ändern dürfen?
Das wiederspricht dem Gedanken der OOP. Stichwort Kapselung von Daten
was ist, wenn foo feststellt, dass z.B. rect über der Grenze liegt und dann einen parameter korrigiert, etwa:
Dann ist das schlechter Programmierstil. So wäre es deutlich besser:
#include <stdexcept> class rect { int width_; int height_; public: void invariant () const { if ((100 < width_) || (100 < height_ )) throw std::runtime_error ("rect::invariant violated"); } rect(int const width, int const height) : width_(width), height_(height) { this->invariant(); } };
-
~john schrieb:
TheShadow2000 schrieb:
ja ich verstehe dass es ein temporäres objekt ist... das ist ok... warum aber sollen die funktionen so ein objekt nicht ändern dürfen?
Das wiederspricht dem Gedanken der OOP. Stichwort Kapselung von Daten
Hä? Was hat Kapselung bitte mit Veränderbarkeit von Daten zu tun? Das sind zwei unterschiedliche Paar Schuhe.
was ist, wenn foo feststellt, dass z.B. rect über der Grenze liegt und dann einen parameter korrigiert, etwa:
Dann ist das schlechter Programmierstil. So wäre es deutlich besser:
Nein, so wäre es nicht besser, denn so erfüllt es seinen Zweck nicht. Du hast jetzt die Invariante fest in der rect-Klasse verdrahtet. Wahrscheinlich sollte das aber gar nicht passieren, sondern die Invariante sollte nur für 'foo' gelten. Abgesehen davon benutzt Du sowieso eine ganz andere Semantik (Fehler werfen). Wer sagt Dir, dass Deine Semantik pauschal besser geeignet ist? Es gibt Situationen für beide Szenarien.
-
Konrad Rudolph schrieb:
Hä? Was hat Kapselung bitte mit Veränderbarkeit von Daten zu tun? Das sind zwei unterschiedliche Paar Schuhe.
Nein, das hängt direkt zusammen. Die Prüfung, ob ein Exemplar einer Klasse ein gültiges Exemplar einer Klasse ist gehört definitiv nicht aus der Klasse ausgelagert! Nach jeder Methode, die ein Objekt verändert gehört automatisch die Invariante aufgerufen, so daß die Korrektheit der Objekte gewährleistet ist. Wenn man die Invariante aus der Klasse auslagert, kann man diese Korrektheit nicht mehr garantieren und die Fehlerquellen nehmen stark zu. -> schlechtes Design
Die Sache ist eindeutig zu beanworten, weil hier eine Verständnisfrage gestellt wurde und keine Anleitung für die Wartung von Altcode gesucht wurde.Nein, so wäre es nicht besser, denn so erfüllt es seinen Zweck nicht. Du hast jetzt die Invariante fest in der rect-Klasse verdrahtet.
Nur so ist es korrektes OOD. "foo" erfüllt in ursprünglichen Variante ausschließlich die Aufgabe einer Invariante. Ergo, gibt es auch kein Begründung für die Auslagerung der Invarianten.
Wer sagt Dir, dass Deine Semantik pauschal besser geeignet ist?
Das ist mittlerweile "common sense" in der C++ Programmierung. Nachzulesen in den diversen C++ Lehrbüchern z.B. aus der "C++ in Depth Series", den diversen Lehrbüchern über OOA, OOD und OOP.
-
(D)Evil, du solltest deinen Codestil mal überdenken
-
Don06 schrieb:
rect(10, 20);erzeugt ein temporäres Object.
richtig.
Don06 schrieb:
Temporäre Objekte sin in C++ immer konstant, sonst wäre zB sowas möglich:
i++++++++++;nein. Weder sind temporäre Objekte per se (und auch nicht in diesem Falle) konstant, noch ist irgendetwas (vom Stil abgesehen) per se falsch mit i++++++++++
rect(10, 20) ist ein rvalue. Da rect zudem eine Klasse ist, verweist dieses rvalue auf ein temporäres Objet vom Typ rect (es ist nicht const-qualifiziert). Nebenbei: rvalues skalarer Typen sind keine Objekte und daher niemals cv-qualifiziert; veränderbar ist nur Speicher und nur Objekte stellen Speicherbereiche dar - dieses rvalues sind nicht konstant in dem Sinne, dass ihr Typ const-qualifiziert wäre (es gibt selbstverständlich temporäre Objekte skalarer Typen, aber diese erscheinen niemals als rvalues).
Der Ausdruck i++++++++ ist mit skalarem Operanden nicht möglich, da das Ergebnis des Postinkrements ein rvalue ist, und dieser Operator nur auf lvalues angewandt werden kann (die zudem modifizierbar=nicht-const sein müssen). Im Falle von Klassen müsste dieser Operator überladen werden - ob daher der Ausdurck i++++++++, falls i ein Klassenobjekt ist, möglich ist, hängt davon ab, wie ++ überladen wurde:Der Objektparameter nicht-statischer Memberfunktionen kann ein lvalue oder ein rvalue sein. Ein Referenz auf const T kann an ein (möglicherweise cv-qualifiziertes) rvalue vom Typ T gebunden werden - dabei wird ggf. ein temporäres Objekt erzeugt. Eine Referenz, deren Typ nicht als const T dargestellt werden kann (also T&, volatile T&, const volatile T&), kann dagegen nicht an ein rvalue vom Typ (cv) T gebunden werden. Diese Festlegung ist willkürlich und soll verhindern, dass Operationen, die ihr Argument verändern sollen, stillschweigend mit temporären Objekten arbeiten.
Wenn unser Postinkrementoperator als Memberfunktion überladen wird, können wir nicht verhindern, dass das Argument ggf. ein rvalue (z.B. als Folge eines vorherigen Aufrufes) sein darf, man müsste dann tricksen, indem man etwa als Ergebnis dieser Funktion ein const T zurückgibt (was zufällig geht, weil rvalues eines Klassentyps gerade const-qualifiziert sein können). Der bessere Weg ist hier die Implementierung als freie Funktion mit einen Referenz als Parameter (oder man findet nichts dabei, rvalues inkrementieren zu können, was ich für durchaus haltbar halte).
-
@~john:
Es ist _ganz sicher nicht_ "common sense" bei verletzten Invarianten einen "runtime_error" (!) zu werfen. Wenn dann gehört da ein "logic_error" geworfen, und auch das wird (gottseidank) in C++ nicht überall so gemacht.
IMO ist die passende Reaktion auf eine verletzte Invariante immer noch std::terminate().BTW: OOP/OOD ist keine Silver-Bullet. Daher kann man auch nicht sagen dass etwas "falsch" oder "schlecht" ist nur weil es sich nicht mit OOP/OOD verträgt.
-
hustbaer schrieb:
@~john:
Es ist _ganz sicher nicht_ "common sense" bei verletzten Invarianten einen "runtime_error" (!) zu werfen.Das "common sense" bezog ich auf die Tatsache, daß eine Invariante Teil der Klasse zu sein hat.
Wenn dann gehört da ein "logic_error" geworfen,
Welche Art der Fehlerbehandlung man anwendet hängt doch sehr stark von der Problemstellung ab. Wenn dies fehlerhafte Nutzerdaten aus Dateien o.ä. sind, die ein Server zu verarbeiten hat, wird man diesen nur wegen kaputter Datensätze nicht terminieren, und einen Logikfehler hat es in so einem Fall ebenfalls nicht gegeben.
Unter Umständen kann es ich so einem Fall (z.B. UNIX) auch sinnvoll sein, daß Programm mittels exit() und speziellem Fehlercode zu beenden (in dem man einen globalen Exceptionhandler installiert), so daß das aufrufenden Programm signalisiert bekommt, daß die Verarbeitung fehlschlug etc.