Topic: instantiating unnamed abstract classes


Author: sbnaran@localhost.localdomain.COM (Siemel Naran)
Date: 1999/01/07
Raw View
On 23 Dec 1998 17:18:11 GMT, AllanW@my-dejanews.com

>I'd like to hear about the case you have in mind. You have a three-tier
>class system, where the first two tiers are abstract, and yet all three
>tiers have a large amount of private data which can be initialized in
>various ways depending on constructor parameters?!?  I don't know
>anything about your project, so I'll ask you to answer honestly: Is it
>just *POSSIBLE* that your design is more complicated that absolutely
>essential?

My real project is not this complicated.  Most classes have just
one constructor, besides the copy constructor.  But sometimes,
classes to have more than one constructor.  For example, you can
initialize a table either from a generating function or from a
sequence code.  Mainly I'm thinking about the future.  It's
possible that when my project grows really big (everything I've
written so far is well less than 10000 lines), the plethora
of constructors could be a little problem.  Incidentally, do
you think that most important changes to the standard arise
from practical considerations or from theoretical
considerations?

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: AllanW@my-dejanews.com
Date: 1998/12/21
Raw View
In article <slrn77hgr2.717.sbnaran@localhost.localdomain>,
  sbnaran@uiuc.edu wrote:
> [...] we should be able to do the following:
>
>    struct Base { virtual ~Base(); Base(int,int); Base(int,int,int); };
>    struct Derived : Base { Derived(const Base&, int); };
>
>    int main() { Base * b = new Derived(Base(1,2),3); }
>
> This is the copy initialization technique for
> initializing derived objects.  It's a nice alternative
> to declaring fifty or so constructors in our derived
> class as in,
>
>    struct Derived { Derived(int,int,int); Derived(int,int,int,int); };
>    // Base part            ^^^^^^^^               ^^^^^^^^^^^
>
> Moreover, I think that it leads to code that is generally easier
> to read, and easier to maintain.  Imagine if class Derived had
> constructors taking fifty arguments!

Interesting idea, but there are other ways to accomplish the same thing.

    struct Base_init {
        Base_init();                 // Result same as Base default ctor
        Base_init(const Base_init&); // Copy the init list about

        // 48 specializations:
        Base_init(int,int);
        Base_init(int,int,int);
        // 46 more

  // Data members used in Base's ctor. // Can be private if Base is a friend
class. int i1, i2, i3;  };  struct Base {  virtual ~Base();  Base();  //
Default ctor, same as Base(Base_init())  Base(const Base&);  // Copy ctor
Base(const Base_init&);  // Replaces the other 48 ctors  };  struct Derived :
public Base {  Derived(int);  // Uses Base default ctor  Derived(const
Base_init&, int); // Uses Base_init to construct Base  };

    // Our example uses two int's to construct the Base,
    // and one more int for the Derived object itself
    int main() {
        Base * b = new Derived(Base_init(1,2),3);
        // ...
        delete b;
    }

The temporary Base_init object holds all the initializers for Base.

> As an aside, we note that on a sufficiently optimizing compiler,
> the copy initialization technique should incur no run time
> penalty.  Due to the return optimization in reverse, the
> temporary object Base(1,2) should be created in place in the
> Derived object.

I suspect that soon (and perhaps now?) many compilers will optimize
away the temporary Base_init object.

> Now we'll explore a potential problem with this method.
> What's to stop someone from doing this:
>    Base(1,2).f(); // call pure virtual function!
> Or even this,
>    void f(const Base& base) { return base.f(); }
>    int main() { f(Base(1,2)); }

With my method, the Base_init object won't have any member functions
(save destructors, et. al.). So we don't need any language extension
to make it illegal to call them.

> us to the second solution.  We just say that calling a
> pure virtual function on an abstract class leads to undefined
> behaviour.  From item 6,

Calling a pure virtual function is well-defined and useful.

--
AllanW@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: AllanW@my-dejanews.com
Date: 1998/12/22
Raw View
Somehow the formatting got messed up.

In article <75mad0$m61$1@nnrp1.dejanews.com>,
  AllanW@my-dejanews.com wrote:
>
> In article <slrn77hgr2.717.sbnaran@localhost.localdomain>,
>   sbnaran@uiuc.edu wrote:
> > [...] we should be able to do the following:
> >
> >    struct Base { virtual ~Base(); Base(int,int); Base(int,int,int); };
> >    struct Derived : Base { Derived(const Base&, int); };
> >
> >    int main() { Base * b = new Derived(Base(1,2),3); }
> >
> > This is the copy initialization technique for
> > initializing derived objects.  It's a nice alternative
> > to declaring fifty or so constructors in our derived
> > class as in,
> >
> >    struct Derived { Derived(int,int,int); Derived(int,int,int,int); };
> >    // Base part            ^^^^^^^^               ^^^^^^^^^^^
> >
> > Moreover, I think that it leads to code that is generally easier
> > to read, and easier to maintain.  Imagine if class Derived had
> > constructors taking fifty arguments!
>
> Interesting idea, but there are other ways to accomplish the same thing.
>
>     struct Base_init {
>         Base_init();                 // Result same as Base default ctor
>         Base_init(const Base_init&); // Copy the init list about
>
>         // 48 specializations:
>         Base_init(int,int);
>         Base_init(int,int,int);
>         // 46 more

          // Data members used in Base's ctor.
          // Can be private if Base is a friend class.
          int i1, i2, i3;
      };

      struct Base {
          virtual ~Base();
          Base();   // Default ctor, same as Base(Base_init())
          Base(const Base&);  // Copy ctor
          Base(const Base_init&); // Replaces the other 48 ctors
      };

      struct Derived : public Base {
          Derived(int);  // Uses Base default ctor
          Derived(const Base_init&x,int); // Use x to construct Base
      };

>     // Our example uses two int's to construct the Base,
>     // and one more int for the Derived object itself
>     int main() {
>         Base * b = new Derived(Base_init(1,2),3);
>         // ...
>         delete b;
>     }
>
> The temporary Base_init object holds all the initializers for Base.
>
> > As an aside, we note that on a sufficiently optimizing compiler,
> > the copy initialization technique should incur no run time
> > penalty.  Due to the return optimization in reverse, the
> > temporary object Base(1,2) should be created in place in the
> > Derived object.
>
> I suspect that soon (and perhaps now?) many compilers will optimize
> away the temporary Base_init object.
>
> > Now we'll explore a potential problem with this method.
> > What's to stop someone from doing this:
> >    Base(1,2).f(); // call pure virtual function!
> > Or even this,
> >    void f(const Base& base) { return base.f(); }
> >    int main() { f(Base(1,2)); }
>
> With my method, the Base_init object won't have any member functions
> (save destructors, et. al.). So we don't need any language extension
> to make it illegal to call them.
>
> > us to the second solution.  We just say that calling a
> > pure virtual function on an abstract class leads to undefined
> > behaviour.  From item 6,
>
> Calling a pure virtual function is well-defined and useful.

--
AllanW@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain.COM (Siemel Naran)
Date: 1998/12/22
Raw View
On 22 Dec 1998 19:51:06 GMT, AllanW@my-dejanews.com
>> In article <slrn77hgr2.717.sbnaran@localhost.localdomain>,

>> > This is the copy initialization technique for
>> > initializing derived objects.  It's a nice alternative
>> > to declaring fifty or so constructors in our derived
>> > class as in,

>> Interesting idea, but there are other ways to accomplish the same thing.

OK.  For maximal organization, we should make the init class a nested
class.  We could have the enclosing class duplicate the variables of
the nested class (method 1), or we could also have the enclosing class
contain an instance of the nested class (method 2).  There are
problems with either method as regards notation.  However, if we allow
abstract classes to be instantiated, the ugly notation goes away.

The only problem with instantiating abstract classes is that one may
use it to call a pure virtual function that has no definition, as in
   AbstractClass(1,2).pure_virtual_function();
I guess this is a case of fraud, not accident.  But C++ must say
something about this, hence my rule that calling a pure virtual
function on an abstract class (may) lead to undefined behaviour.


>> Calling a pure virtual function is well-defined and useful.

If you have a definition of the pure virtual function, yes.
Quite often, Derived::f() does some stuff specific to class
Derived, then invokes Base::f().  And Base::f(), maybe even
Derived::f(), are be pure virtual.




Let's examine your proposal.  Suppose we have a base class Base
which is abstract.  It has a nested class Base::Init that has
46 or so constructors.  Then there is a constructor
   explicit Base::Base(const Base::Init&);

Method 1 says that we write code to transfer variables from
Base::Init to Base (eg, copy Base::Init::x into Base::x).  This
is tedious and error prone.  Also, if the constructor is not
inline, it is possible that the optimization of eliminating
redundant copies won't be done -- that is, we copy the
variable from outside into Base::Init::x, and thence into
Base::x.  Moreover, if the class Base::Init contains a
pointer to implementation, do we copy the pointer from
Base::Init to Base, or do we clone it?  For with the Init
technique, it is reasonable to write:
   Base::Init k(new int);
   Base b1(k); // pointer inside 'k' should be cloned
   Base b2(k); // ditto

Method 2 says that class Base contains an instance of a
Base::Init.  So there's no tedious copying.  However, there
still is the problem of whether the Base::Init class should
copy or clone its pointer to implementation.  But there's
an another problem.  What happens if the derived class
contains an instance of a Derived::Init?  Now Derived::Init
either derives from Base::Init or contains a Base::Init.
Hence the field Base::Init would be contained twice -- once
in class Base and once in class Derived.  Here's what I
mean more explicitly:

class Base
{
   public:
      struct Init
      {
         public:
            explicit Init(int xy) : x(xy), y(xy) { }
            Init(int x_, int y_) : x(x_), y(y_) { }

         private:
            int x,y;
      };

     explicit Base(const Init&);


  private:
     //int x,y; // option 1
     Init xy; // option 2
};

class Derived : public Base
{
   public:
      class Init : public Base::Init
      {
         public:
            Init(const Base::Init& x_, int y_, int z_)
                 : Base::Init(x_), y(y_), z(z_)
            {
            }

         private:
            int y,z;
      };

      explicit Derived(const Derived::Init& i) : Base(i), yz(i)
      {
      }

   private:
      //int y,z; // option 1
      Init yz; // option 2
};


We see that a Derived object contains a Base::Init sub-object
twice, once in class Base and once in class Derived.  The way
to solve this problem is to decouple the class::Init
objects.  That is, Derived::Init should not contain a
Base::Init.  Hence, we have this

class Base { class Init { }; explicit Base(const Init&); };

class Derived : public Base
{
     class Init { }; // note: decoupled to Base::Init
     Derived(const Base::Init&, const Derived::Init&);
     virtual ~Derived() = 0;
};

This works, but I really want single arg constructors.  Suppose
we have a class Final that derives from Derived, I would rather
write,
   Final f(Derived(Base(1,2),3),4,5,6);
than
   Final f(Base::Init(1,2),Derived::Init(3),Final::Init(4,5,6));
In some cases, I would prefer the second longer method, but not
in the case I have in mind.  And the issue of whether to copy
or clone pointers is also important.


Either way, client code looks less readable, which is a minus.

In summary, I don't see any deep reason to disallow unnamed abstract
classes.

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: AllanW@my-dejanews.com
Date: 1998/12/23
Raw View
In article <slrn7805g3.6g8.sbnaran@localhost.localdomain>,
  sbnaran@uiuc.edu wrote:
>
> On 22 Dec 1998 19:51:06 GMT, AllanW@my-dejanews.com
> >> In article <slrn77hgr2.717.sbnaran@localhost.localdomain>,
>
> >> > This is the copy initialization technique for
> >> > initializing derived objects.  It's a nice alternative
> >> > to declaring fifty or so constructors in our derived
> >> > class as in,
>
> >> Interesting idea, but there are other ways to accomplish the same thing.
[Then Allan supplied an example using a class Base_init.]
>
> OK.  For maximal organization, we should make the init class a nested
> class.  We could have the enclosing class duplicate the variables of
> the nested class (method 1), or we could also have the enclosing class
> contain an instance of the nested class (method 2).

I would just have it duplicate the constructor arguments, to the extent
that is possible, and then have the Base constructor extract what it
needed for the "real" initialization.

> There are
> problems with either method as regards notation.  However, if we allow
> abstract classes to be instantiated, the ugly notation goes away.
>
> The only problem with instantiating abstract classes is that one may
> use it to call a pure virtual function that has no definition, as in
>    AbstractClass(1,2).pure_virtual_function();
> I guess this is a case of fraud, not accident.  But C++ must say
> something about this, hence my rule that calling a pure virtual
> function on an abstract class (may) lead to undefined behaviour.

I disagree; this might be a simple mistake, not fraud.

Let's say you manipulate an array of Vehicle pointers. You want to
instanciate a Bus. If I remember your proposal, we can instanciate
a Vehicle so long as we don't give it a name. So we instanciate a
Vehicle, and take it's address. Now we've got a valid Vehicle
pointer which happens to point to a non-concrete Vehicle. Why
wouldn't you (mistakenly) perform operations on it?

You also seem to be assuming two things here:

  1. There are a large number of private members of the abstract
     class.
  2. All, or at least most, of these variables get their values
     from trivial operations (such as copies) of the constructor
     arguments, or whatever we devise to replace that mechanism.

But this seems to contradict your original assumption, which is:

  3. We need to have many different constructors because of the
     many different ways we can initialize the abstract class's
     private data.

> Let's examine your proposal.  Suppose we have a base class Base
> which is abstract.  It has a nested class Base::Init that has

Used to have

> 46 or so constructors.  Then there is a constructor

We replace most or all of them with a single constructor taking
an initialization structure. Probably the initialization structure
still has 46 constructors, but we've moved them outside the Base
class so that we can inherit this behavior in Derived classes.

>    explicit Base::Base(const Base::Init&);
>
> Method 1 says that we write code to transfer variables from
> Base::Init to Base (eg, copy Base::Init::x into Base::x).  This
> is tedious and error prone.

I can't believe that you'd consider this more error-prone than
the 46 "old" constructors. Especially if the init structure
contains constructor-argument-replacements, rather than
private-data replacements.

> Also, if the constructor is not
> inline, it is possible that the optimization of eliminating
> redundant copies won't be done -- that is, we copy the
> variable from outside into Base::Init::x, and thence into
> Base::x.

If the initialization is a straight copy, this might be quite
acceptable. If it isn't, then why isn't the constructor inline?

> Moreover, if the class Base::Init contains a
> pointer to implementation, do we copy the pointer from
> Base::Init to Base, or do we clone it?  For with the Init
> technique, it is reasonable to write:
>    Base::Init k(new int);
>    Base b1(k); // pointer inside 'k' should be cloned
>    Base b2(k); // ditto

This problem is neither better nor worse than the same problem
using an instanciated abstract base to perform initialization.
And notice that your initialization object has a name: k.
As I recall, you wouldn't allow abstract objects to have a name.

> Method 2 says that class Base contains an instance of a
> Base::Init.  So there's no tedious copying.  However, there
> still is the problem of whether the Base::Init class should
> copy or clone its pointer to implementation.

True. I don't think I would favor this option too often, unless
the base class really did carry around a copy of all the ctor
arguments (but then why do you have 46 ctors?).

> But there's
> an another problem.  What happens if the derived class
> contains an instance of a Derived::Init?  Now Derived::Init
> either derives from Base::Init or contains a Base::Init.
> Hence the field Base::Init would be contained twice -- once
> in class Base and once in class Derived.  Here's what I
> mean more explicitly:

The derived class is already going to get a Base::Init object.
If the derived class has few additional data members, then
Derived::Init shouldn't exist at all. But if the derived class
does need a large number of additional data members, then
Derived::Init should NOT be descended from (or contain a)
Base::Init because of the very problem we're trying to solve --
exponential curve in the numnber of constructors. Instead, we'll
construct a Derived with two arguments: A Base::Init and a
Derived::Init.

> This works, but I really want single arg constructors.  Suppose
> we have a class Final that derives from Derived, I would rather
> write,
>    Final f(Derived(Base(1,2),3),4,5,6);
> than
>    Final f(Base::Init(1,2),Derived::Init(3),Final::Init(4,5,6));

Why?

[Note too that these lines are not the same the second uses a
Final::Init while the first one supplies the same arguments
directly.]

If Derived has only a few more ctor arguments than Base, and Final
has only a few more ctor arguments than Derived, then how about:
     Final f(Base::Init(1,2),3,4,5,6);

> In some cases, I would prefer the second longer method, but not
> in the case I have in mind.  And the issue of whether to copy
> or clone pointers is also important.

I'd like to hear about the case you have in mind. You have a three-tier
class system, where the first two tiers are abstract, and yet all three
tiers have a large amount of private data which can be initialized in
various ways depending on constructor parameters?!?  I don't know
anything about your project, so I'll ask you to answer honestly: Is it
just *POSSIBLE* that your design is more complicated that absolutely
essential?

If your data really is trivially initialized, then you should be
able to reduce the number of constructors.  Consider:
    class Base {
        int m_a, m_b;
    public:
        Base(int a, int b) : m_a(a), m_b(b) {}
        virtual int sum() = 0; // Abstract
    };
    int Base::sum() { return m_a + m_b; }

    class Derived : public Base {
        int m_c;
    public:
        Derived(int a, int b, int c) : Base(a,b), m_c(c) {}
        virtual int sum() = 0; // Abstract
    }
    int Derived::sum() { return Base::sum() + m_c; }

    class Final : public Derived {
        int m_d, m_e, m_f;
    public:
        Final(int a, int b, int c, int d, int e, int f)
            : Derived(a,b,c), m_d(d), m_e(e), m_f(f) {}
        // Concrete
        virtual int sum() { return Derived::sum() + m_d + m_e + m_f; }
    };
    int main() {
        Final f(1,2,3,4,5,6);
        std::cout << f.sum();
    }

Note that each of the three classes has only ONE constructor. Why do
you need more?

--
AllanW@my-dejanews.com is a "Spam Magnet" -- never read.
Please reply in USENET only, sorry.

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/       Search, Read, Discuss, or Start Your Own


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Vlad Harchev" <vladhar@imimail.ssau.ru>
Date: 1998/12/18
Raw View

Siemel Naran writes in message ...
> ....

>So my conclusion is that you should be able to create
>unnamed instances of abstract classes, and any attempt
>to call a pure virtual function on this instance leads
>to undefined behaviour.
>
>So, what does everyone think?
>

Here are my thoughts:
1)  The functionality (sharing ctros) can be achieved using the dirty hack -
making the base class non-abstract, via defining all pure virtual methods as
writing the errror message (or so) - this can be automated.

2)  I think that in OOP abstract base classes mostly provide description of
the interface the ancestors of them will provide, and they  mostly don't
have any ctros declared (at least fifty ctors is extremely rare).

3) How compiler should destroy the instance of abstract base class
constructed on the stack if it has pure virtual destructor?

4) The term 'abstract base classe' will be slightly ambiguated by allowing
creation of the unnamed abstract base classes.

So, I think that
 a)functionality of proposed feature  is achievable using other techniques
without considerable perfomance degradation (of code and  of programmer)
 b) has some logical inconsistences

-Vlad



[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain.COM (Siemel Naran)
Date: 1998/12/19
Raw View
On 18 Dec 1998 15:53:13 GMT, Barry Margolin <barmar@bbnplanet.com> wrote:
>Siemel Naran <sbnaran@uiuc.edu> wrote:

>>Now we'll explore a potential problem with this method.
>>What's to stop someone from doing this:
>>   Base(1,2).f(); // call pure virtual function!
>>Or even this,
>>   void f(const Base& base) { return base.f(); }
>>   int main() { f(Base(1,2)); }

>What about:
>
>    void f(const Base& base) { g(&base); }
>    void g(base *baseptr) { baseptr->f(); } // call pure virtual function!
>
>Would you prohibit taking the address of an abstract class instance?

I don't understand your point.

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain.COM (Siemel Naran)
Date: 1998/12/19
Raw View
On 18 Dec 1998 16:11:31 GMT, Vlad Harchev <vladhar@imimail.ssau.ru> wrote:
>Siemel Naran writes in message ...

>>So my conclusion is that you should be able to create
>>unnamed instances of abstract classes, and any attempt
>>to call a pure virtual function on this instance leads
>>to undefined behaviour.
>>
>>So, what does everyone think?


>Here are my thoughts:
>1)  The functionality (sharing ctros) can be achieved using the dirty hack -
>making the base class non-abstract, via defining all pure virtual methods as
>writing the errror message (or so) - this can be automated.

True, but messy.  I should have written about this in my long post
too, because it was on my mind.  For example,

struct DummyBase : Base
{
     virtual void f() const { abort(); }
}

int main() { Derived d(DummyBase(1,2),3); } // works


Our immediate observation is that it is messy -- we may have to
define so many pure virtual functions with 'abort()' in the
body and dummy return values (to avoid generally useful compiler
warning messages about missing returns).

Moreover, we see that the DummyBase solution provides no new
functionality.  So why doesn't the core language provide the
feature.  It would be easy to add this feature.



>2)  I think that in OOP abstract base classes mostly provide description of
>the interface the ancestors of them will provide, and they  mostly don't
>have any ctros declared (at least fifty ctors is extremely rare).

Yes, fifty was a little of a hyperbole.  Here's the common case I
have in mind.  The most base class is a protocol class -- that is,
it has only pure virtual functions, a virtual destructor, and no
data (definition from Lakos).  The class next up in the inheritance
hierarchy derives from this base class, and overrides a few pure
virtual functions, maybe adds some more pure virtual functions,
and adds data.  Because it has data, it has a couple of constructors.
The class next up in the inheritance hierarchy derives from this
previous class, and adds more data, more constructors, and most
likely overrides a few more virtual functions, and possibly adds
some more.

If each class defines three constructors, then in an inheritance
tree four levels deep, the number of constructors is pow(3,3)==27
(note that the most base class has no constructors, so the answer
is not pow(3,4)).

Forgot to mention this too.  A good design rule is to make
non-leaf classes abstract -- this is from Scott Meyers
"More Effective C++".  I use this rule, and it helps me write
cleaner code.  However, initializing the most derived classes is
somewhat of a pain.  Actually, egcs allow this, and so I have
no problem.



>3) How compiler should destroy the instance of abstract base class
>constructed on the stack if it has pure virtual destructor?

The way it does it now.  The pure virtual destructor is required to
have a definition, otherwise you get a link time error.  Consider
this:

struct X
{
     virtual ~X() = 0;
};

//X::~X() { }

struct Y : X
{
     virtual ~Y() { }
};

int main()
{
     //X x; // error: can't create object on stack
     Y y; // ok: compiles
}

The code compiles, but does not link.  Using como:

l.o: In function `Y::_dt( (void))':
l.o(.text+0x1a1): undefined reference to `X::_dt( (void))'
l.o(.data+0x7c): undefined reference to `X::_dt( (void))'
l.o(.data+0xa0): undefined reference to `X::_dt( (void))'

Using egcs:

/tmp/ccLHhlMv.o: In function `Y::~Y(void)':
/tmp/ccLHhlMv.o(.gnu.linkonce.t._._1Y+0x14): undefined reference to
`X::~X(void)'
/tmp/ccLHhlMv.o: In function `Y::Y(void)':
/tmp/ccLHhlMv.o(.Y::gnu.linkonce.t.(void)+0x2c): undefined reference to
`X::~X(void)'
collect2: ld returned 1 exit status



Thus, we must supply a definition of the pure virtual destructor
anyway.  I suppose that since the result of calling a pure
virtual function is undefined, the compiler could give a runtime
error, like a message "pure virtual function called" followed by
an abort().  But maybe the rule is different for destructors,
and a definition is required -- egcs and como are excellent
compilers, so I assume that they are conforming in most ways.

However, the optimization I have in mind is as follows.  The
unnamed temporary will be create in place in its final location.
This avoids a call to the copy constructor, and a call to the
destructor.  It is the return value optimization in reverse.



>4) The term 'abstract base classe' will be slightly ambiguated by allowing
>creation of the unnamed abstract base classes.

Not sure what you mean.  IMHO, the rule would be useful.


--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain (Siemel Naran)
Date: 1998/12/17
Raw View

The standard prohibits instantiating abstract classes.  For
example, if class Base is abstract, these are illegal:

   struct Base { virtual ~Base(); virtual void f() const = 0; };

   Base base; // error: can't create object on stack
   void function(Base); // error: can't receive on stack
   Base action(); // error: can't return on stack

The above three examples appear in section 10.4, item 3.
To this, we should add a fourth example.

   Base * base=new Base(); // error: can't create object on heap


So we see that we can't use abstract classes as value types.
But of course, we, or rather the compiler, should be able to
create instances of abstract classes as sub-objects of a
concrete, derived class.  For example,

   struct Derived : public Base { void f() const; };

Creating a Derived object on the stack indirectly creates
the Base part on the stack too.  (Our Base class is empty,
but in reality, abstract classes may contain data.  Yes,
the root or most base class will normally be data-less,
but derived classes may be abstract but still contain
data.)  From item 2 in the same section:

2  An abstract class is a class that can be used only as a
   base class of some other class; no objects of an abstract
   class can be created except as sub-objects of a class
   derived from it.


In view of the above, it seems that we should be able to
do the following:

   struct Base { virtual ~Base(); Base(int,int); Base(int,int,int); };
   struct Derived : Base { Derived(const Base&, int); };

   int main() { Base * b = new Derived(Base(1,2),3); }

This is the copy initialization technique for
initializing derived objects.  It's a nice alternative
to declaring fifty or so constructors in our derived
class as in,

   struct Derived { Derived(int,int,int); Derived(int,int,int,int); };
   // Base part            ^^^^^^^^               ^^^^^^^^^^^

Moreover, I think that it leads to code that is generally easier
to read, and easier to maintain.  Imagine if class Derived had
constructors taking fifty arguments!

As an aside, we note that on a sufficiently optimizing compiler,
the copy initialization technique should incur no run time
penalty.  Due to the return optimization in reverse, the
temporary object Base(1,2) should be created in place in the
Derived object.


Egcs lets you allow you to create unnamed instances of abstract
classes for the purposes of copy initialization.  That is, it
does not let you get away with the three error examples given at
the top of my message.  But it let's you create unnamed instances.
I recently learned that this feature of egcs is a bug.  The
compiler correctly rejects an attempt to instantiate an
unnamed class.

However, the ability to create unnamed instances seems to be
extremely useful, and seems to fit in with general object
oriented programming style.  So I think that egcs has it
right.



Now we'll explore a potential problem with this method.
What's to stop someone from doing this:
   Base(1,2).f(); // call pure virtual function!
Or even this,
   void f(const Base& base) { return base.f(); }
   int main() { f(Base(1,2)); }

There are two solutions to this dilemma.  We could
require the compiler to flag the above as errors.
That is, unnamed instances can be created so long
as we don't call functions on them, or pass them
to functions.  One problem with this rule is that
constructors are functions, and the Derived
constructor may look like this,
   Derived::Derived(const Base& base, int) : Base() { base.f(); }

Another reason why the above solution is not good is that
it is not a context free rule.  That is, the interpretation
of whether it is ok to create the abstract class depends
on the context in which it is used.

But it would be a good idea for compilers to flag the above
cases as warnings, at least the first example.  Which brings
us to the second solution.  We just say that calling a
pure virtual function on an abstract class leads to undefined
behaviour.  From item 6,

6  Member functions can be called from a constructor (or
   destructor) of an abstract class; the effect of making
   a virtual call (10.3) to a pure virtual function
   directly or indirectly for the object being created
   (or destroyed) from such a constructor (or destructor)
   is undefined.


This means that the following is legal but leads to
undefined results -- on my computer, the error message
"pure virtual function called" if the pure virtual
function was not defined.
   Base::Base() { f(); } // call pure virtual function!

A compiler should give a warning of the above code.  It
could give an error too, but strictly speaking, this is
non-conforming behaviour.  So the compiler could just
as well allow this code, but give a warning (or an
error in non-conforming mode):
   Base().f(); // call pure virtual function!


So my conclusion is that you should be able to create
unnamed instances of abstract classes, and any attempt
to call a pure virtual function on this instance leads
to undefined behaviour.

So, what does everyone think?

--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Barry Margolin <barmar@bbnplanet.com>
Date: 1998/12/18
Raw View
In article <slrn77hgr2.717.sbnaran@localhost.localdomain>,
Siemel Naran <sbnaran@uiuc.edu> wrote:
>Now we'll explore a potential problem with this method.
>What's to stop someone from doing this:
>   Base(1,2).f(); // call pure virtual function!
>Or even this,
>   void f(const Base& base) { return base.f(); }
>   int main() { f(Base(1,2)); }

What about:

    void f(const Base& base) { g(&base); }
    void g(base *baseptr) { baseptr->f(); } // call pure virtual function!

Would you prohibit taking the address of an abstract class instance?

--
Barry Margolin, barmar@bbnplanet.com
GTE Internetworking, Powered by BBN, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Don't bother cc'ing followups to me.


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]