www.digitalmars.com         C & C++   DMDScript  

D - C++ destructor [was Properties]

reply "Patrick Down" <pdown austin.rr.com> writes:
"Martin York" <Martin.York veritas.com> wrote in message
news:a1vpav$1rgj$1 digitaldaemon.com...
 What big advantages are in creating objects on stack, anyhow?
IMHO: Because you know exactly when destructors are being called, resource management (memory but mainly other) is excellent when using stack based objects.
Yes, but this is really using a side effect of a feature of C++ to solve a larger problem. How do you encode a partial algorithm where some of the steps are predefined but some of the steps are not. The most common place you see this type of thing is when some object has to be setup, used, and shut down correctly. File handling is an example. You must: A. Open the file B. Read/Write/Process the data C. Close the file Step A is well defined. Step C must always be done. But step B always depends on the particular application. A C++ programmer might attack the problem like this: class FileReader { FILE* fin; FileReader(const char* szName) { fin = fopen(szName,"r"); if(!fin) throw FileError(); } ~FileReader() { if(fin) fclose(fin); } Read(...) Write(...) }; This is over simplified example. The point is this if the object is used as an automatic stack based object the destructor semantics takes care of closing the file. However this is not the only way or even the best way to attack this type problem. Other languages have more elegant ways of handling this. Take Ruby for example. Here is the regular way to write to a file. Despite being written in Ruby we should all recognize the steps. aFile = File.new("test","w") aFile.puts "Hello" aFile.close But we can also write the same program in Ruby like this: File.open("test","w") do |aFile| aFile.puts "Hello" end The function open above is analogous to a static function call on the File class. It opens the file in write mode. What is not obvious from above is that the code block that follows the open is a hidden parameter to the open function. It's like the code block that follows the open was turned into a function and a reference to that function was passed as a parameter to the open function. The open function above is written in Ruby like this. def open(filename,mode) aFile = File.new(filename.mode) yield aFile aFile.close end The yield statement gives control to the code block that was passed as a hidden parameter to the open function. The aFile object is passed as a parameter to the code block. The Ruby open function neatly hides having to close the file. Accessing the elements in a container can be done similarly in Ruby. Compare it to what you would need to do in STL. anArray.each { |i| print i, } Enough tooting Ruby's horn. Can you accomplish the same types of things in C/C++? Well yes and there's actually an example of this from C's stdlib. qsort. void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) ); int MyCompare(void* elem1,void* elem2) { return *(int *)elem1 < *(int *)elem2; } //... somewhere else int a[10]; qsort(a,10,sizeof(a[0]),MyCompare); The complexity of the quick sort is hidden in the qsort function. All you supply the comparison function. But the comparison function is in a different part of the code from the place where the sort is done. You can tell that the array is being quick sorted but you can't tell by what criteria unless you go look for MyCompare. When I code something like this the pseudo code in my head has two key concepts: 1) Sort type ( Not the implementation details. ) 2) Sort criteria These two concepts are linked in my head but I am forced to separate them when implementing as above in C. The file examples above could be done similarly. ReadFile(char* name, void (*Proc)(FILE*) ) { FILE* fin = fopen(name,"r"); if(fin) { Proc(fin); fclose(fin); } } void MyFileProcessing(FILE* fin) { // Do stuff with the file } // Somewhere else ... ReadFile("somefile",MyFileProcessing); But this suffers from the same problems as the qsort example above. There's also the STL functors. int a[10]; bubble_sort(a,10, less<int>()); Cool! That's pretty readable. Uh huh, now try to make a composite functor that sorts and array of object pointers by comparing the results of calling a member function of the object with a parameter. Even with STL you often end up having to fall back to creating a separate function to accomplish what you want. Wouldn't a syntax like this be more readable? SomeClass* a[10]; bubble_sort(a,10) { |SomeClass* x,SomeClass* y| return x->mf(1) < y->mf(1); } So what is the point of all my rambling? 1) Exploiting the semantics of the destructor to automatically execute code at the end of a function block is not possible in D like it was in C++. 2) I think that using the destructor this way was a little quirky to begin with so I don't consider it a big loss. 3) Some sort of syntax for defining implicit functions, in place where they are used in the code, would be useful. This could be used in situations that the destructor was using in above as well as a bunch of other situations.
Jan 15 2002
next sibling parent "Walter" <walter digitalmars.com> writes:
"Patrick Down" <pdown austin.rr.com> wrote in message
news:a22q7i$p3c$1 digitaldaemon.com...
 3) Some sort of syntax for defining implicit functions, in place where
they
 are used in the code, would be useful.  This could be used in situations
 that the destructor was using in above as well as a bunch of other
 situations.
There have been many requests for this. It's a good idea, and would be worthwhile to add to D.
Jan 15 2002
prev sibling parent reply "Juan Carlos Arevalo Baeza" <jcab roningames.com> writes:
"Patrick Down" <pdown austin.rr.com> wrote in message
news:a22q7i$p3c$1 digitaldaemon.com...
 You must:
 A. Open the file
 B. Read/Write/Process the data
 C. Close the file

 A C++ programmer might attack the problem like this:

 class FileReader
 {
   FILE* fin;
   FileReader(const char* szName)
   {
     fin = fopen(szName,"r");
     if(!fin)
       throw FileError();
   }
   ~FileReader()
   {
     if(fin)
       fclose(fin);
   }
   Read(...)
   Write(...)
 };
In this one case (a very thin wrapper), I'd rather do: class File { FILE* fin; File(const File&); // Disallowed File& operator=(const File&); //Disallowed public: File(const char* szName, const char* szMode) { fin = fopen(szName,szMode); if(!fin) throw FileError(); } ~File() { if(fin) // This "if" is actually not needed. fclose(fin); } operator FILE*() const { return fin; } }; This would be used as such: { File aFile("test"); fprintf(aFile, "Hello"); }
 The open function above is written in Ruby like this.

 def open(filename,mode)
   aFile = File.new(filename.mode)
   yield aFile
   aFile.close
 end

 But we can also write the same program in Ruby like this:

 File.open("test","w") do |aFile|
   aFile.puts "Hello"
 end
The only two clear advantages of the version in Ruby are in the definition part, not in the usage: - The Ruby way would allow you to do more things than just "wrap" the block. You could call it several times, for example. - The Ruby way has a much more concise way to implement the definition. This goes together with what I said in an earlier post. C++ is a toolbag (not even a toolbox, where things can be at least well organized). I'm using the "class" tool to implement a "smarter" file handle with better syntax. In a way, the same thing is true for Ruby, only the tool is a very different one. The main advantage of the C++ way of doing it is that it can do other things. For example: class BigObject { File cacheFile; ... }; In this case, a "big object" uses a file on the disk to cache data. It keeps that file open as long as it is alive, and closes it automatically. The Ruby way using blocks and "yield" cannot do this kind of thing. My point being that classes with destructors are one of the tools in C++, and they are, indeed, useful, and not having them in a language does limit the possibilities of the programmer. IMHO. Maybe the ideal language would be multi-level, where different capabilities are hidden or expressed in different ways to different kinds of users: the high-level programmer, the library programmer, the systems programmer, etc... Like having different but highly inter-related languages. Salutaciones, JCAB
Jan 16 2002
parent reply "Walter" <walter digitalmars.com> writes:
I know this is beside the point of resource deallocation via destructor, but
in D reading a file is as simple as:

    buffer = file.read("mydatafile.txt");

"Juan Carlos Arevalo Baeza" <jcab roningames.com> wrote in message
news:a24nvs$2229$1 digitaldaemon.com...
 "Patrick Down" <pdown austin.rr.com> wrote in message
 news:a22q7i$p3c$1 digitaldaemon.com...
 You must:
 A. Open the file
 B. Read/Write/Process the data
 C. Close the file

 A C++ programmer might attack the problem like this:

 class FileReader
 {
   FILE* fin;
   FileReader(const char* szName)
   {
     fin = fopen(szName,"r");
     if(!fin)
       throw FileError();
   }
   ~FileReader()
   {
     if(fin)
       fclose(fin);
   }
   Read(...)
   Write(...)
 };
In this one case (a very thin wrapper), I'd rather do: class File { FILE* fin; File(const File&); // Disallowed File& operator=(const File&); file://Disallowed public: File(const char* szName, const char* szMode) { fin = fopen(szName,szMode); if(!fin) throw FileError(); } ~File() { if(fin) // This "if" is actually not needed. fclose(fin); } operator FILE*() const { return fin; } }; This would be used as such: { File aFile("test"); fprintf(aFile, "Hello"); }
 The open function above is written in Ruby like this.

 def open(filename,mode)
   aFile = File.new(filename.mode)
   yield aFile
   aFile.close
 end

 But we can also write the same program in Ruby like this:

 File.open("test","w") do |aFile|
   aFile.puts "Hello"
 end
The only two clear advantages of the version in Ruby are in the definition part, not in the usage: - The Ruby way would allow you to do more things than just "wrap" the
block.
 You could call it several times, for example.
 - The Ruby way has a much more concise way to implement the definition.

    This goes together with what I said in an earlier post. C++ is a
toolbag
 (not even a toolbox, where things can be at least well organized). I'm
using
 the "class" tool to implement a "smarter" file handle with better syntax.
In
 a way, the same thing is true for Ruby, only the tool is a very different
 one.

    The main advantage of the C++ way of doing it is that it can do other
 things. For example:

 class BigObject {
   File cacheFile;
   ...
 };

    In this case, a "big object" uses a file on the disk to cache data. It
 keeps that file open as long as it is alive, and closes it automatically.
 The Ruby way using blocks and "yield" cannot do this kind of thing.

    My point being that classes with destructors are one of the tools in
C++,
 and
 they are, indeed, useful, and not having them in a language does limit the
 possibilities of the programmer. IMHO.

    Maybe the ideal language would be multi-level, where different
 capabilities are hidden or expressed in different ways to different kinds
of
 users: the high-level programmer, the library programmer, the systems
 programmer, etc... Like having different but highly inter-related
languages.
 Salutaciones,
                          JCAB
Jan 16 2002
parent "Pavel Minayev" <evilone omen.ru> writes:
"Walter" <walter digitalmars.com> wrote in message
news:a24v01$26ji$1 digitaldaemon.com...
 I know this is beside the point of resource deallocation via destructor,
but
 in D reading a file is as simple as:

     buffer = file.read("mydatafile.txt");
Or, if you like streams (like I do =)), you can use my version: File file = new File("mydatafile.txt") buffer = new ubyte[file.size()]; file.read(buffer, file.size()); file.close(); By the way, it uses destructors to close opened files automatically...
Jan 17 2002