Topic: suggestions: about constructors and destructors


Author: danielgutson@hotmail.com (danielgutson@hotmail.com)
Date: Sat, 25 Jan 2003 21:01:55 +0000 (UTC)
Raw View
allan_w@my-dejanews.com (Allan W) wrote in message news:<7f2735a5.0301241511.20024ed6@posting.google.com>...
> danielgutson@hotmail.com (danielgutson@hotmail.com) wrote
> > Newgroupers:
>
> ??

's' missing. I don't have an English++ compiler here :)

Before starting my comments, a very good friend told me about the lack
of 'motivation' in my proposals descriptions. And it is absolutely
trueth.
Sorry about this, I did not expected you are sorcerers for guessing
why I'm proposing these. From now on, I'll include purposes, reasons
and motivations in my postings.

>
> In your example, it looks like you aren't actually using the int.

Too light example. I agree. I often use 'destroy' function (with
delete this) for reference counting. And I hope that my lines in this
message will differentiate the concept.

> You just want the destructor to have a different "signature",
> so that you can't destroy the "Contained" object normally.

50% and 50%: first, I do want to make hiding available also for
destructors (there comes your observation about signature); second, I
want to make clear that my class 'needs' such parameter/s for
destruction, and that this is the only way to destroing it (I'm
writing in 'example' mode).

Let me think an example (not too good for this). Suppose there is a
'handleManager', a class that gives you a handle to something, and
once you finish your usage, you 'return' it.

class HandlesManager
{ public:
    Handle getHandle();
    void endOfUse(Handle h);
  ...
};

and that a class uses such handles, and you don't want to keep a
reference to the HandlesManager nor make it a singleton (for any
architectural reason).
So, you pass the manager to its constructor, and to the destructor:

class AUser
{
public:
   AUser(HandlesManager& mgr)
     :_h ( mgr.getHandle() ) {}

   ~AUser(HandlesManager& mgr)
   { mgr.endOfUse( _h );    }

private:
  const Handle _h;
};

Consecuences:
   1) readability: the idea is self-documented, needs it for all the
life of the instance of AUser, gets it at the beginning, ends it at
the end.
   2) I think this is more natural that making private the destructor
and providing a function 'destroy'-like: we are using the native
concept of the destructor! Why should we redefine the wheel if the
extension is natural? Constructor ANALOGOUS Destructor. Constructor
ACCEPTS parameters, but Destructor NOT(ACCEPTS) parameters, then the
analogy is incomplete.
  3) you can have different [overloaded] destructors (accepting
different parameters).

Suppose you have a default constructor, and a non-default destructor.
Then it's ok for me that this is not valid:

void f()
{
   AUser a;

} // error: no default destructor available

But, a rule (I agree this could be questionable) could be that if the
called constructor has the same signature that the destructor, then
the same parameters shoud be used: [considering the original AUser]

void f(HandlesManager& mgr)
{
   AUser a(mgr);

} // a.~AUser(mgr) is invoked here

>     void foo() {
>         Contained c; // Would be illegal, 'cause you can't destroy it.
>         Contained *d = new Contained; // Legal
>         delete d;                     // Not legal ("default destructor")
>         delete(1) d;                  // Legal
>     }
> Is this what you had in mind?

Just a warn: I want to differentiate a non-default destructor against
overloading the delete operator. (In the same sense we differentiate
new overloading from constructor overloading).

>
> Conventional wisdom is: An object ought to know it's own state. Once
> you destroy it, there is no more state. Therefore, there's no reason
> to pass arguments to the destructor.

Indeed, the destruction is a process itself when you still have the
object.
And providing parameters to its [last] process seems natural to me, as
far as it's just another process. For example, you can provide hints
about 'how to destruct' the object.
The destruction is requested by the client, and the client might want
to provide such information.
Another complain of the 'destroy' operation, is that you must
duplicate an already existent [native] mechanism, you must perform
your 'proprietary' destruction, for example when you have a virtual
destructor, you must also make your 'destroy' virtual.

This is my general position: I hate to make parallel things that the
language does just because it is not general enough. If it already
provides the concept, but is not general, let's generalize it instead
of making [THE SAME]+[extension].

> Same, except you'd use p.destroy(1) at the end instead of delete p(1).
> Besides -- what happens if S defines operator()? Your statement above
> would call operator(), and delete whatever it returns...

Absolutely correct. I forgot about this. Help me finding an
alternative syntax :)   (in few days I'll post it if you don't want;
but I invite you to mail to my personal email address if you want, and
please consider this as a friendly act)

> >      struct C
> >      {
> >         const C() { ... };
> >         C() { ... };
> >      };
>
> If this WAS legal C++, the const keyword ought to come after.

NO: I already thought about this.

C() const  denotes that you cannot change the content of this in the
process.
But during construction, you ARE changing and assigning things. The
class becomes const immediately after your construction process ends.
Same thing for destructor.
In the example I gave you lines above about the HandlesManager, this
still holds: we can write

class AUser
{
public:
   const AUser(HandlesManager& mgr) { ... }
   const ~AUser( same thing ...
};

The only thing you change within this class is the assignment of the
 const Handle _h
attribute, that takes place during the construction.

Providing *only* a const constructor, hides the default constructor,
and forces the 'read only' usage of the class.
I think that passive classes are a good example for this.

Another (esthetical) reason of placing the 'const' at the left, is
that looks equal to the declaration:
  const AUser a;        // instance decl.

looks like

{
  const AUser( ... );   //constructor decl.


> Still don't understand the motivation for this.

I hope I was clearer this time. I consider your no-understanding as my
fault (please, I expect I don't sound ironic)

> >    void g(void)
> >    {
> >       A a[10](constr_index<0>);
> >       A b[10][20](constr_index<0>+constr_index<1>);
> >    }

>
> And this I don't get at all. Seems like an elaborate mechanism that would

sorry, let me clarify:

I mean by the 'constr_index<n>' keyword, that the compiler will pass
the i-th position (the index) in that parameter while invoking the
constructor, where 'n' is the n-th dimension.

> only be appropriate when the constructor takes a single int parameter.
> I'd rather see operator new[] accept a functor.

No, I don't want to mess with the operator new. I associate it to the
memory allocation specific instead of the construction process once
you get the memory allocated.

> Even then, it would only work when the Customer object has a single
> parameter -- rather special-purpose, I should think. And does it

that's the 'n' parameter in 'constr_index<n>'.

If you want an object member of an array, accepting the index in a
parameter and another value in other:

class Element
{ public:
  Element( size_t index, float otherThing);
};

then:

  Element e[10]( constr_index<0>, 3.1415 );

The compiler just passes the 'index number' of the first (and unique)
dimension of this in the first parameter.
<0> means the first dimension. If you have a 2x2 matrix, you will be
able to use
     constr_index<0> and constr_index<1>

> solve a problem that std::vector<> can't also solve?

I didn't understand. I think it will be clear with the above
explanation; otherwise, please let me know.

 Daniel.

pd: thanks for your feedback, very good.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: danielgutson@hotmail.com (danielgutson@hotmail.com)
Date: Sun, 26 Jan 2003 20:06:33 +0000 (UTC)
Raw View
allan_w@my-dejanews.com (Allan W) wrote in message news:<7f2735a5.0301241511.20024ed6@posting.google.com>...
[...]
> >           S* p = new S;
> >           delete p(1);   //ok
> >      }
>
> Same, except you'd use p.destroy(1) at the end instead of delete p(1).
> Besides -- what happens if S defines operator()? Your statement above
> would call operator(), and delete whatever it returns...

I realized later about this: it's not applicable since p is a pointer,
and has no operator () defined.

 Daniel.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: allan_w@my-dejanews.com (Allan W)
Date: Fri, 24 Jan 2003 23:13:42 +0000 (UTC)
Raw View
danielgutson@hotmail.com (danielgutson@hotmail.com) wrote
> Newgroupers:

??

>  Please let me know comments about the following issues.
>
> - overloading non-default destructors and destruction lists.
>
>   * consecuences:
>       1) when defined, non-default destr. hides the default
> destructor, unless it is explicited (in the same sense the non-default
> constructor does)
>       2) non-default destructor only calleable through delete operator
> and/or destruction list (for contained classes)
>   * example1:
>
>      struct Contained
>      {
>          ~Contained(int);
>      };
>
>      struct Container
>      {
>         Contained c;
>
>         ~Container()
>              : ~c(1)    //only way
>         {}
>      };

In your example, it looks like you aren't actually using the int.
You just want the destructor to have a different "signature",
so that you can't destroy the "Contained" object normally.
    void foo() {
        Contained c; // Would be illegal, 'cause you can't destroy it.
        Contained *d = new Contained; // Legal
        delete d;                     // Not legal ("default destructor")
        delete(1) d;                  // Legal
    }
Is this what you had in mind?

If so, I can offer an alternative:
    struct Contained {
    private:
        ~Contained() {} // Private destructor
    public:
        // When other routines want to destroy it, they call destroy
        void destroy() { // or void destroy(int) if you like
           delete this;  // Calls private destructor
        }
    };

Conventional wisdom is: An object ought to know it's own state. Once
you destroy it, there is no more state. Therefore, there's no reason
to pass arguments to the destructor.

There is such a thing as "placement delete," which (if I understand
correctly) is used only when a "placement new" expression fails in
the constructor. Note that operator delete is different than the
destructor, which (as far as I know) never currently gets parameters.

>   * example2:
>      struct S { ~S(int); };
>
>      void f(void)
>      {
>           S s;    // invalid: no default destructor available
>           S* p = new S;
>           delete p(1);   //ok
>      }

Same, except you'd use p.destroy(1) at the end instead of delete p(1).
Besides -- what happens if S defines operator()? Your statement above
would call operator(), and delete whatever it returns...

> - 'const' constructor/destructor overloading:
>      -> Provide the differentiation between constructing/destruction a
> const and non const objects, using the 'const' keyword as a prefix of
> the constructor declaration

I don't understand the point of this one.

>      struct C
>      {
>         const C() { ... };
>         C() { ... };
>      };

If this WAS legal C++, the const keyword ought to come after.
    struct C {
        C() const { ... } // I want to KNOW I'm a const object
        C() { ... }       // I'm NOT a const object
   };
To be consistent with the notation for const functions.

>      void f()
>      {
>           const C c1;  //invokes C::const C();
>           C c2;        // invokes C::C()
>      }

Still don't understand the motivation for this.

> - non-default constructors for arrays:
>         { A(int); };
>
>         void f(void)
>         {
>            A a[10](1);
>            A b[10][20](2);
>         }
>
>     Same applicable through the new operator (I'm not talking about
> overloading the 'new').

To construct an array of objects with the same initializer. I guess
this makes sense, though we've all gotten pretty good at structuring
our code so this isn't necessary -- it would still be convenient.
Your syntax is reasonable, too.

>     Suggestion: a keyword for sending the 'index value' in a parameter
> of the constructor. Let's sat constr_index<int Dim>, where Dim denotes
> the dimension number. Dim should be a constant expression.
> Example using struct A (defined above)
>
>    void g(void)
>    {
>       A a[10](constr_index<0>);
>       A b[10][20](constr_index<0>+constr_index<1>);
>    }
>
>    will construct:
>      a: an array of 10 A's, receiving the consecuent index number in
> the constructor.
>      b: a 10x20 matrix, accepting the sum of the row and the column in
> the constructor.

And this I don't get at all. Seems like an elaborate mechanism that would
only be appropriate when the constructor takes a single int parameter.
I'd rather see operator new[] accept a functor.

    struct Customer {
        //...
        Customer(std::string custname);
        //...
    };
    std::string getname(); // Gets customer names from database
    Customer custlist[25](getname);

Even then, it would only work when the Customer object has a single
parameter -- rather special-purpose, I should think. And does it
solve a problem that std::vector<> can't also solve?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: philippe_mori@hotmail.com ("Philippe Mori")
Date: Sat, 25 Jan 2003 02:30:14 +0000 (UTC)
Raw View
>
> In your example, it looks like you aren't actually using the int.
> You just want the destructor to have a different "signature",
> so that you can't destroy the "Contained" object normally.
>     void foo() {
>         Contained c; // Would be illegal, 'cause you can't destroy it.
>         Contained *d = new Contained; // Legal
>         delete d;                     // Not legal ("default destructor")
>         delete(1) d;                  // Legal
>     }
> Is this what you had in mind?
>
> If so, I can offer an alternative:
>     struct Contained {
>     private:
>         ~Contained() {} // Private destructor
>     public:
>         // When other routines want to destroy it, they call destroy
>         void destroy() { // or void destroy(int) if you like
>            delete this;  // Calls private destructor
>         }
>     };
>
> Conventional wisdom is: An object ought to know it's own state. Once
> you destroy it, there is no more state. Therefore, there's no reason
> to pass arguments to the destructor.
>

This is also what I think... If you need diffrent destruction policies, then
make different classes for each cases (provided that the argument only
serve to select proper destructor). This could be a template class with a
specialization for the destructor...

class Base { };    // No destruction
class Der1 : public Base { ~Der1() {} };
class Der2 : public Base { ~Der2() {} };

and uses the appropriate class in special situation.

Or you may have a Base that do not do any destruction, a derived class
that does standard destruction and a member function (of Base) for your
custiom destruction. When you want custom destruction, you uses Base
class object and call explicitly the destruction function. Otherwise, in
general case, you uses the derived class.

Another alternatif is to have a flag in the base class that indicate if
standard destruction should be done. The flag would be true. But if
you call your custom function, you set the flag top false and do nothing
in the destructor.

I do not think that it worth a language changes... particulary that we
should not have such design in general. And for an exception, special
code is an alternative...

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: danielgutson@hotmail.com (danielgutson@hotmail.com)
Date: Thu, 23 Jan 2003 17:49:07 +0000 (UTC)
Raw View
Newgroupers:
 Please let me know comments about the following issues.

- overloading non-default destructors and destruction lists.

  * consecuences:
      1) when defined, non-default destr. hides the default
destructor, unless it is explicited (in the same sense the non-default
constructor does)
      2) non-default destructor only calleable through delete operator
and/or destruction list (for contained classes)
  * example1:

     struct Contained
     {
         ~Contained(int);
     };

     struct Container
     {
        Contained c;

        ~Container()
             : ~c(1)    //only way
        {}
     };

  * example2:
     struct S { ~S(int); };

     void f(void)
     {
          S s;    // invalid: no default destructor available
          S* p = new S;
          delete p(1);   //ok
     }

- 'const' constructor/destructor overloading:
     -> Provide the differentiation between constructing/destruction a
const and non const objects, using the 'const' keyword as a prefix of
the constructor declaration
   example:
     struct C
     {
        const C() { ... };
        C() { ... };
     };

     void f()
     {
          const C c1;  //invokes C::const C();
          C c2;        // invokes C::C()
     }

    Following proposal above (about destructor), same thing should be
available for destructors: (redefining the class)

     struct C
     {
        const C() { ... };
        C() { ... };

       ~C();
       const ~C();
     };

    -> proposed rules:
            * When constructing a const object, if there is no 'const
constructor' available, the non-const is used
            * When destructing a const object, if a const constructor
was provided, then a const destructor should also be provided

- non-default constructors for arrays:
    I'm talking about
        struct A
        { A(int); };

        void f(void)
        {
           A a[10](1);
           A b[10][20](2);
        }

    Same applicable through the new operator (I'm not talking about
overloading the 'new').

    Suggestion: a keyword for sending the 'index value' in a parameter
of the constructor. Let's sat constr_index<int Dim>, where Dim denotes
the dimension number. Dim should be a constant expression.
Example using struct A (defined above)

   void g(void)
   {
      A a[10](constr_index<0>);
      A b[10][20](constr_index<0>+constr_index<1>);
   }

   will construct:
     a: an array of 10 A's, receiving the consecuent index number in
the constructor.
     b: a 10x20 matrix, accepting the sum of the row and the column in
the constructor.


I'd like to listen to your feedbacks,

thanks!

  Daniel.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]