[linux-l] Re: Ruby: sehr cool, aber laaaahm... wie geht's schneller?! - D?

Steffen Dettmer steffen at dett.de
Mi Aug 30 02:10:52 CEST 2006


* Oliver Bandel wrote on Mon, Aug 28, 2006 at 00:31 +0200:
> > und solange sind die verbundenen Resourcen blockiert - evtl. viel zu
> > lange...
> 
> Vielleicht hat Java da ja ein besonderes Problem.

Ja, es hat nicht aus C++ gelernt :-)

> > In Java und sicherlich in anderen GC-Sprachen hat man auf einmal ein
> > open und ein close (weil Constructor und Destructor, bzw. letzerer)
> > nicht verlässlich ist. Unnatürlich. naja.
> 
> Kannst Du das mal genauer spezifizieren?
> Was meinst Du mit open/close hier?
> 
> Files?

Na ja, zum Beispiel. Ich persönlich finde folgendes anschaulich:

class Connection;
class Connector
{
   public:
     Connection*
     connect(void);

     disconnect(Connection*);
};

class Connection
{
   public:
     size_t
     read(ByteBuffer, Timeouts, ...);
     
     size_t
     write(ByteBuffer, Timeouts, ...);
};

Also in C++ machste:

TcpConnector connector(tcpParameter);
Connection   connection(connector.connect());

connection->read(...); // usw.
connector.disconnect(connection); // deletes connection

In Java machste vielleicht:

TcpConnector connector(tcpParameter);
Connection   connection(connector.connect());

connection.open();
connection->read(...); // usw.
connection.close(); // kann auch disconnect machen
connector.disconnect(connection); // connection bleibt erstmal

Nu kann der Connection-destructor "sicher" implementiert werden (er ruft
dann connect auf), oder, noch besseres Beispiel dafür (aber vielleicht
will man das /hier/ nicht, klar, ist halt nur ein Beispiel).

{
        TcpConnection connection(tcpParameter);
        
        connection.read(...);

        // brauchste kein delete, kein close, nix, geht einfach!
}

Am Ende des Blockes ist sichergestellt, dass die TCP-Verbindung
ordentlich geschlossen ist, selbst wenn bei read ne Exception auftritt
usw. Genauso mit auto_ptr.

In java braucht's ein close() (weil finalize ja nicht garantiert wird).
Dann macht man auch ein open() wegen der Symetrie (obwohl construction
ohne destruction ja schon nicht symetrisch ist). Kommt raus:

try {
        Connection connection = new TcpConnection(tcpParameter);

        connection.open(); // was für 'ne Verbindung war die
                           //   geschlossene TCP Verbindung eigentlich?
                           // Macht logisch keinen Sinn.
                           // Man könnte sogar read vor'm open aufrufen
                           //   das sollte man besser (in C++) durch 
                           //   Design lösen. Gibts eine Connection, kann
                           //   man auch read aufrufen.
        connection.read(...);
} finally {
        connection.close(); // iii
}

> Und welches Problem hast Du damit?

Meiner Meinung nach braucht Java eh nur den finally-Hack, weil es keine
Destructoren gibt. In C++ muss ich das nicht hinschreiben (siehe oben),
es geht einfach. In Java muss ich das hinschreiben (wegen Zwangs-GC).
Und das soll ein Feature sein, dass ich das hinschreiben muss? Super :-)

Ich kann keine Resourcen im "scope" allokieren. In C++ kann ich
schreiben:

{
    Mutex lock(*this);
}

lockt für den Block (gut, genau Mutex geht in Java mit synchronised,
aber das ganze Schlüsselwort ist nur ein Hack wegen Zwangs-GC). Damit
geht.

Java:

// Temporär auf Events subscriben
try {
    EventFacade dispatcher(this); // Ein Observer, der Events auf "mein"
                                   //   Interface adaptiert
    
    dispatcher.attach(eventSource);
} finally {
    dispatcher.detach();
}

(Gut, attach wäre eine eventSource-Method, egal hier)

In C++:

{
    EventFacade dispatcher(*this, eventSource);
}

Viel eleganter, oder?

Lebenszeit ist auch wichtig. Im Beispiel sei ein Teil einer Methode
gezeigt:

Java:

try {
    MyExpensiveClass my(); // sei teuer und soll kurzlebig sein

    my.getEventSource().attach(this);
    my = null;  // nur zeigen, dass es weg soll, Anweisung macht nix
} finally {
    // Bloss nie vergessen, sonst lebt "my" ewig!
    my.getEventSource().detachAll();
}

In C++:

{
    MyExpensiveClass my();
    my.getEventSource().attach(*this);
}

"this" lebt auf jeden Fall länger als my() (weil Methode, klar).
detachAll() kann ~MyExpensiveClass (destructor) machen. Ich kann nichts
falsch machen. my überlebt die Methode auf keinen Fall und es kann
nichts schiefgehen. Man kann das sehen, auch in komplexen Beispielen, wo
man vielleicht viele Objektinstanzen in einem Block braucht oder so. Ich
muss nicht alle im finally suchen oder so, ich weiss, sie können einfach
nicht überleben oder unerwünschte Referenzen kennen.

Im Connector-Beispiel oben macht der ~Connector natürlich ein
disconnectAll(). Damit ist sichergestellt, keine Verbindung zu
vergessen. Der Destructor loggt, falls noch Connections da waren, aber
selbst dann gehen nie die Filedescriptoren aus. Egal ob Exeptions oder
sonstwas.

Und wenn eine andere Klasse auf die Idee kommen sollte, sich eine
Connector-Referenz zu speichern, knallts wenigstens. In java hat man oft
Sachen wie:

class ComplexObject;
class EvenMoreComplexObject
{
  public:
    ComplexObject&
    getComplexObject(void);

  private:
    ComplexObject o_;
};

Dann schreibt jemand sowas wie:

EvenMoreComplexObject cat;
AnotherComplexObject  dog;

dog.setComplexObject(cat.getComplexObject());

Ein Desaster! Sowas sieht man meiner Meinung nach oft, wenn nicht klar
ist, wer wem gehört und wie lange lebt. Also speichert sich dog die
Referenz auf "cat.o_". Bei Java lebt damit o_ evtl. länger als cat!

Gut, das Beispiel ist wohl zu abstrakt. Nehmen wir an, die Katze sei ein
Datenmodell, darin eine Tabelle von Stammdaten, der Hund sei ein View
oder sowas. Nun ist das Datenmodell ungültig, der View kann aber
problemlos noch die alten Stammdaten anzeigen.

In C++ könnte der Destructor des Datenmodells mindestens gesichert dafür
sorgen, dass der View das weiss, aber dann braucht man gegenseitige
Referenzen, und sowas ist oft falsch.

Gern leitet man das Modell vom einem Interface ab und übergibt dieses an
den View. Das konkrete Modell kann View kennen, ohne das es zirkulär
wäre. Kenn ich von C++ als "gängige Praxis" und "ähh?! Ja, wie denn
sonst?" und von Java als das neue tolle DesignPattern
"InversionOfControl". SCNR.

Überhaupt speichert man sich in Java wohl gern Referenzen und hat sehr
oft setValue/getValue, die weiter nichts machen. Da kann man doch value
gleich public machen! Aber das ist in der Java-Welt verschrieen, kein
reines OO. In C++ ist das ja zum Glück in Ordnung, weil multi-paradigm.
In C++ kann man ja auch eine struct mit Methoden haben, so ne Art
"doch-nicht-ganz-Klasse" oder sowas. In Java geht das alles nicht, alles
beerbt java.lang.Object und in generics kann ich keine "int"s packen und
was da noch alles nicht geht (Beschränkungen bei inner classes in
statics usw.).

oki,

Steffen

-- 
Dieses Schreiben wurde maschinell erstellt,
es trägt daher weder Unterschrift noch Siegel.





Mehr Informationen über die Mailingliste linux-l