Type-safety violation using pointers to superclasses

Type-safety violation using pointers to superclasses

Post by Ewan Mell » Wed, 20 Nov 2002 19:30:52



This question is related to the FAQ's 21.2 and 21.5, but since it
involves neither pointers to pointers nor the arrays-are-pointers
feature that C++ inherited from C it seems that it is sufficiently
novel to be posted here.

Consider the following code:

#include <iostream>
using std::cerr;

class A
{
public:
  A(int x_)
  {
    x = x_;
  }

  int x;

Quote:};  

class B : public A
{
public:
  B() : A(1)
  {
  }

Quote:};

class C : public A
{
public:
  C() : A(2), y(3)
  {
  }

  int y;

Quote:};

void f(A * a)
{
  *a = * new B();   // (1)

Quote:}

int main(void)
{
  C c;

  f(&c);

  cerr << c.x << endl;
  cerr << c.y << endl;

Quote:}

This program prints

1
3

What I think happens is that the line marked (1) creates a new B, but
then uses A's copy constructor to copy the object into *a.  This means
that the B gets sliced down to just the x=1.  However, this gets
copied into a C, since *a is actually c, so you end up with a C that
could never have been created in a type-safe manner.

To put it another way, the * type constructor seems covariant with
respect to subtyping - X <: Y implies X* <: Y* when in fact that type
constructor must be invariant if it is to be safe to assign through
the pointer.

Is this code legal?  Is its behaviour defined?  Should my compiler
issue a warning here?  (g++ -Wall -W passes the code with no
objections).


      [ about comp.lang.c++.moderated. First time posters: do this! ]

 
 
 

Type-safety violation using pointers to superclasses

Post by Ron Natali » Thu, 21 Nov 2002 06:58:22



> What I think happens is that the line marked (1) creates a new B, but
> then uses A's copy constructor to copy the object into *a.

Close, it uses the assignment operator.

Quote:> This means
> that the B gets sliced down to just the x=1.  However, this gets
> copied into a C, since *a is actually c, so you end up with a C that
> could never have been created in a type-safe manner.

Creation is not an issue.   C is created via it's constructor.   What you have
is the assignment of the values from the A portion of a B object to the
A portion of a C object.

No type safety has been violated.


      [ about comp.lang.c++.moderated. First time posters: do this! ]

 
 
 

Type-safety violation using pointers to superclasses

Post by Michael D. Borghard » Thu, 21 Nov 2002 07:00:13


Hi.

a Point to C is passed into the function which converts a C into an A then
the anonymous B is cast a cast into an A and then the assignment operator is
called.

So only A part of B is assigned into the A part is C - OK.

It looks to me that from a language point of view it would be legal but
perhaps not from a semantic point of view.


Quote:

> void f(A * a)
> {
>   *a = * new B();   // (1)
> }

> int main(void)
> {
>   C c;

>   f(&c);

>   cerr << c.x << endl;
>   cerr << c.y << endl;
> }

> This program prints

> 1
> 3


      [ about comp.lang.c++.moderated. First time posters: do this! ]
 
 
 

Type-safety violation using pointers to superclasses

Post by Raoul Goug » Thu, 21 Nov 2002 07:01:58



Quote:> This question is related to the FAQ's 21.2 and 21.5, but since it
> involves neither pointers to pointers nor the arrays-are-pointers
> feature that C++ inherited from C it seems that it is sufficiently
> novel to be posted here.
[snip code]
> What I think happens is that the line marked (1) creates a new B,
but
> then uses A's copy constructor to copy the object into *a.  This
means
> that the B gets sliced down to just the x=1.  However, this gets
> copied into a C, since *a is actually c, so you end up with a C that
> could never have been created in a type-safe manner.

> To put it another way, the * type constructor seems covariant with
> respect to subtyping - X <: Y implies X* <: Y* when in fact that
type
> constructor must be invariant if it is to be safe to assign through
> the pointer.

> Is this code legal?  Is its behaviour defined?  Should my compiler
> issue a warning here?  (g++ -Wall -W passes the code with no
> objections).

This is the "Liskov Substitution Principle" at work. You've used
public derivation, implying that C "is-a" A. That means that any
function that takes a reference to an A object _should_ also work
correctly with a reference to the A part of a C object. The interface
to A currently includes an assignment operator, so this should also
work when applied to *part* of a C object. If that produces an
internal inconsistency in the C object, then C "is-not-really-a" A.
The problem is easy to avoid, either by restricting A's interface or
by not publically inheriting. The problem also doesn't arise if
you avoid having any data members in a base class, which seems
like a good design principle anyway.

Regards,
Raoul Gough.


      [ about comp.lang.c++.moderated. First time posters: do this! ]

 
 
 

Type-safety violation using pointers to superclasses

Post by Alf P. Steinba » Fri, 22 Nov 2002 01:50:33



 >This question is related to the FAQ's 21.2 and 21.5, but since it
 >involves neither pointers to pointers nor the arrays-are-pointers
 >feature that C++ inherited from C it seems that it is sufficiently
 >novel to be posted here.
 >
 >Consider the following code:
 >
 >#include <iostream>
 >using std::cerr;
 >
 >class A
 >{
 >public:
 >  A(int x_)
 >  {
 >    x = x_;
 >  }
 >
 >  int x;
 >};
 >
 >class B : public A
 >{
 >public:
 >  B() : A(1)
 >  {
 >  }
 >};
 >
 >class C : public A
 >{
 >public:
 >  C() : A(2), y(3)
 >  {
 >  }
 >
 >  int y;
 >};
 >
 >void f(A * a)
 >{
 >  *a = * new B();   // (1)
 >}
 >
 >int main(void)
 >{
 >  C c;
 >
 >  f(&c);
 >
 >  cerr << c.x << endl;
 >  cerr << c.y << endl;
 >}
 >
 >This program prints
 >
 >1
 >3
 >
 >What I think happens is that the line marked (1) creates a new B, but
 >then uses A's copy constructor to copy the object into *a.  This means
 >that the B gets sliced down to just the x=1.  However, this gets
 >copied into a C, since *a is actually c, so you end up with a C that
 >could never have been created in a type-safe manner.

Conceptually correct.  If you substitute "assignment operator" for
"copy constructor", then also technically correct.  Nit, picked.

The problem you've run into is the general one that assignment via
an object reference (whether it's a C++ pointer or a C++ reference)
is not type-safe in the presence of polymorphism.

As an academic exercise it's amusing to think about what makes
assignment so special.  Essentially it is that the object assigned
from must be the same type as the object assigned to.  If you then
think of assignment as a virtual function you see that it (and any
other function with this parameter requirement) clashes with
polymorphism  -- how C++ programmers manage without a clear view of
this is (and all discussion show that they do) is a great mystery!

Eiffel solves this problem *for assignment* by differentiating
between _expanded_ objects and _reference_ objects, and an object
must be one or the other, never both, never neither:

   Expanded object:
   - Lives on the stack or as part of another object.
   - Supports assignment.
   - Prohibits reference creation.

   Reference object:
   - Is dynamically allocated.
   - Prohibits assignment (to the object itself, but rules about
     expanded and reference objects apply recursively to parts).
   - Supports reference creation.

Java and C# use a restricted form of this solution (as in very
early Eiffel), where some built-in types are expanded (only),
and all other types are reference types (only).  In fact, if
you look at the actual languages sans syntax then Java is
early --Eiffel++, and C# is --Java++.  Well, in my opinion... ;-)

 >To put it another way, the * type constructor seems covariant with
 >respect to subtyping - X <: Y implies X* <: Y* when in fact that type
 >constructor must be invariant if it is to be safe to assign through
 >the pointer.

Harumph.  I sincerely dislike academic notation and most especially
the terms "variant" and "covariant" flung about with no reference to
IN or OUT  --  the requirements are opposite in those cases, so not
stating which case applies makes for a nearly meaningless uttering.
That is, I sincerely dislike when it's just assumed that any reader
will share the same notation and terminology; it just is not so.

 >Is this code legal?

Yes, I think so, but put it through a decent compiler to check that.
g++ gives a good indication.  comeau even better.

 >Is its behaviour defined?

Yes.

 > Should my compiler
 >issue a warning here?  (g++ -Wall -W passes the code with no
 >objections).

That's up to the compiler, IOW., it's a QOI issue.

Cheers, & hth.,

- Alf


      [ about comp.lang.c++.moderated. First time posters: do this! ]

 
 
 

Type-safety violation using pointers to superclasses

Post by Ewan Mell » Sun, 24 Nov 2002 10:41:25



> [snip]
> What I think happens is that the line marked (1) creates a new B, but
> then uses A's copy constructor to copy the object into *a.  This means
> that the B gets sliced down to just the x=1.  However, this gets
> copied into a C, since *a is actually c, so you end up with a C that
> could never have been created in a type-safe manner.
> [snip]

Thank you all for your responses; they have been enlightening.

The mistake I made was to think about assignment as if it were a
mechanism outside of any particular class, as if it were assignment of
mere object references, for example.  However, as you have all pointed
out, this is not the case.  Raoul Gough put it best, I think, when he
said that I need to regard the assignment operator as part of the
interface of the class (even when that operator is implicitly
generated).  If C is unwilling to support A's assignment operator,
then it is not meeting A's guarantees, and so should not inherit from
A.  I've never seen this emphasised before, and it's certainly not a
design issue that I've considered in the past.

Thanks again,

Ewan Mellor.


      [ about comp.lang.c++.moderated. First time posters: do this! ]

 
 
 

Type-safety violation using pointers to superclasses

Post by Daniel Schül » Fri, 13 Dec 2002 01:07:25


Quote:> a Point to C is passed into the function which converts a C into an A
then
> the anonymous B is cast a cast into an A and then the assignment

operator
is

Won't you get storage leak, by not deleting "anonymous B" ?
For that reason I would say "-Wall" should warn in this case.

--

Daniel.


      [ about comp.lang.c++.moderated. First time posters: do this! ]

 
 
 

1. subclass type compatible with superclass type?

In the following, isn't anything of type B also of
type A (B is derived from A)? Why then do I get the
warning below? Note that I do not get the warning
on the line containing the second new operation.
I am using g++ (GCC) 3.1.

class A {
public:
   virtual int operator()(int) const = 0;

class B : public A {
   virtual int operator()(int) const {return 0;}

int main() {
   A **a = new B*[10];
// f.cc:11: warning: invalid conversion from`B**' to `A**'

   for (int i = 0; i < 10; i++)
     a[i] = new B[10];
   return int(a[0][0](0));

--w


      [ about comp.lang.c++.moderated. First time posters: do this! ]

2. Help

3. Hi all - new to smalltalk [type services and type-safety]

4. UK tides timetable 4 revo

5. C++0x: enum type-safety

6. Up to date sources for cvw's c68

7. Defect Report: bind1st/bind2nd type-safety

8. Finding Fonts for Internationalization FAQ

9. Protocols and Type-Safety

10. Casting a COM pointer to its superclass?

11. Using #import type Smart Pointers With Local Project?

12. Type safety is a joke in MFC

13. Failure of type safety system to catch error -- why?