Thread overview
C++ destructor [was Properties]
Jan 16, 2002
Patrick Down
Jan 16, 2002
Walter
Jan 16, 2002
Walter
Jan 17, 2002
Pavel Minayev
January 16, 2002
"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.













January 16, 2002
"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.



January 16, 2002
"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





January 16, 2002
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
>
>
>
>
>


January 17, 2002
"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...