Teaching new tricks to an old dog (C++ -->Ada)

  • Thread starter Turamnvia Suouriviaskimatta
  • Start date
D

Dmitry A. Kazakov

Dmitry A. Kazakov said:
Ludovic Brentawrites: [...]
package Ada.Exceptions.Extra is
type Extra_Information is abstract tagged null record;

procedure Raise_Exception (E : in Exception_Id;
Information : in Extra_Information'Class);

function Exception_Information (X : Exception_Occurrence)
return Extra_Information'Class;
end Ada.Exceptions.Extra;
[...]

A pair of simple questions:

1. Dispatching to non-existing methods of Extra_Information?

You'd have to "down-cast", which would raise Constraint_Error if the
Extra_Information was not of the expected type.

What if Extra_Information is extended:

type My_Extra_Information is new Extra_Information with private;
procedure Say (Info : My_Extra_Information);

At some non-library level Say gets overridden. Then this new object is
attached to an exception which is propagated out of the scope of the
override. A handler then makes a dispatching call to Say. What happens?

One possible solution is to force the attached object to the base class
when the child type gets out of scope. But then things become even more
interesting, because Say might be abstract! Then object need to be
partially finalized etc. That would be quite sort of C++! :)-))

Another solution is to route the exception to Program_Error when anything
from My_Extra_Information gets out of scope. This would be bullet proof,
but I don't believe many would enjoy it.
I would say that the actual type (that extends Exra_Information) must
be visible in the exception handler and at the point of
Raise_Exception.

But how would you check it? The exception handler knows nothing about any
derived types. Yet it can dispatch ... to something that no more exists.
I hadn't thought about that. For the mechanism to be useful, I
suppose that finalization would have to occur at the end of the
exception handler.

I thought about controlled components and pointers to controlled objects.
They may get out of scope before a handler catches the exception.
However it may not be desirable that
Extra_Information be controlled, as this would make it possible for
Deallocate to raise an exception.

The Extra_Information doesn't have to be protected, it can contain an
access to a protected object. This would make it quite easy to raise
two copies of it in two tasks.

Also it will be two copies of Extra_Information? I definitely do not like
it. This would mean that the behavior will vary depending on where
exception was raised.

I do prefer present by-value semantics of Ada model. But if it gets
extended, and the direction is tagged types, then that is another
semantics: tagged types are by-reference. The only possible logical
consequence of this is that user-defined exception objects have to be
protected.
I would say, at the end of the last exception handler (i.e. the one
that doesn't reraise the exception).

OK that means that the mechanics will be pretty inefficient. Upon
Raise_Exception, the object will always be copied.
What do you have in mind?

Let's consider a model based on [sub]types. If there are exception
contracts then all possible exception types are known in each handler
context. If you don't allow open-end things like exception'Class in
contracts then there is nothing to surprise you in any handler. OK, that
would probably be too rigid. We could relax it by allowing classes
constrained to ranges of types: sort of

type E1 is new exception with ...;
....
type EN is new EN-1 with ...;

procedure Foo
exception E1'Class (EN) | Constraint_Error | ...;

or

procedure Foo
exception E1..EN | Constraint_Error | ...;

The point is that Foo knows what it will raise, the handler knows if the
body may call Foo. Further Foo cannot raise anything defined within Foo.
Same is applicable to any scope. If I try

declare
My_Exception is new exception;
begin
...
end;

Then I'll get compile error telling that My_Exception may propagate out of
the scope.

declare
My_Exception is new exception;
Old_Style_Exception : exception; -- That's OK, no new type here
begin
...
exception
when My_Exception =>
... -- Alright now
end;
 
I

Ioannis Vranos

Ludovic said:
^
Isn't there a missing { here?

No.



And a missing } here? In which case I don't see any propagation of
your exception up the stack, as there is only one stack frame.


The code is ISO C++ compliant as it is. The exception on the stack works in whatever stack
depth as far as the copy constructor is accessible (exists and is public).

Since you don't provide a copy constructor, only a shallow copy takes
place.


Which is the copying of the pointer value.

Where is the message string allocated, and how do you know it
is still accessible when you catch the exception in a location remote
from the throw statement?


The string literal exists in an implementation defined space and is guaranteed to be
accessible as long as it gets accessed.

Also, copying the exception once for each stack frame seems quite
inefficient to me. I doubt that compiler writers would do that,
especially in the general case of C++ where the exception object can
be arbitrarily large.


Yes the compiler writers are free and do optimise this. However the copy constructor is
required to be accessible for those who do not optimise it.



--
Ioannis Vranos

http://www23.brinkster.com/noicys

[I am using 90 characters word-wrapping - (800/640) *72= 90 or better described as:
(800/640) *80 - 10 for quotation= 90. If someone finds it inconvenient, please let me know].
 
I

Ioannis Vranos

Dave said:
Where available, alloca does allocate on the stack. AFAICS this can
do, at a lower/less-safe level, everything Ada can do at function
scope. It can't do allocation _and deallocation_ for a block within a
function, nor cross-function like function-return-unconstrained. It
almost has to be special-cased/inlined so it should be efficient.


In C++ you can always do:

#include <new>


class SomeClass
{
};


void somefunc()
{
unsigned char obj[sizeof(SomeClass)];

SomeClass *p= new(obj)SomeClass;

delete p;
}


as also:


#include <new>


class SomeClass
{
};


SomeClass *somefunc()
{
static unsigned char obj[sizeof(SomeClass)];

return new(obj)SomeClass;
}


int main()
{
SomeClass *p= somefunc();

delete p;
}


Here the object is created in the stack.


--
Ioannis Vranos

http://www23.brinkster.com/noicys

[I am using 90 characters word-wrapping - (800/640) *72= 90 or better described as:
(800/640) *80 - 10 for quotation= 90. If someone finds it inconvenient, please let me know].
 
R

REH

Ioannis Vranos said:
In C++ you can always do:

#include <new>


class SomeClass
{
};


void somefunc()
{
unsigned char obj[sizeof(SomeClass)];

SomeClass *p= new(obj)SomeClass;

delete p;
}


as also:


#include <new>


class SomeClass
{
};


SomeClass *somefunc()
{
static unsigned char obj[sizeof(SomeClass)];

return new(obj)SomeClass;
}


int main()
{
SomeClass *p= somefunc();

delete p;
}


Here the object is created in the stack.
These will mostly fail on systems with alignment constraints (SPARC, PPC,
etc.).
 
I

Ioannis Vranos

Ioannis said:
In C++ you can always do:

#include <new>


class SomeClass
{
};


void somefunc()
{
unsigned char obj[sizeof(SomeClass)];

SomeClass *p= new(obj)SomeClass;

delete p;
}


as also:


#include <new>


class SomeClass
{
};


SomeClass *somefunc()
{
static unsigned char obj[sizeof(SomeClass)];

return new(obj)SomeClass;
}


int main()
{
SomeClass *p= somefunc();

delete p;
}


Here the object is created in the stack.


Of course, one can also do:

#include <iostream>
#include <new>


class SomeClassA
{
int array[1024];
};

class SomeClassB
{
public:
SomeClassB() { std::cout<<"Constructor called!\n"; }
~SomeClassB() { std::cout<<"Destructor called!\n"; }
};


void somefunc()
{
SomeClassA objA;

SomeClassB *p= new(&objA) SomeClassB;

delete p;
}



int main()
{
somefunc();
}


corrupting objA in this way and invoking undefined behaviour.

But C++ is an enabling language, and not a prohibiting language. You can really do
whatever you want, and provides some protection from accidental mistakes only, and not
from intentional coding.




--
Ioannis Vranos

http://www23.brinkster.com/noicys

[I am using 90 characters word-wrapping - (800/640) *72= 90 or better described as:
(800/640) *80 - 10 for quotation= 90. If someone finds it inconvenient, please let me know].
 
P

Pascal Obry

Ioannis Vranos said:
You can't protect a bad programmer from doing bad programming, unless you
forbid him to continue programming. :)

But you can propose a easy path that is safer. In this case there is lot of
more chance than the programmer will choose it. Easier is always appreciated :)
And a language must make it harder to do unsafe thing, like deallocation on
Ada. You have a instanciate Unchecked_Deallocation. The meaning is clear and
nobody can do that without noticing.

As Ludovic I appreciate a lot the help of the compiler and never try to fight
it. If it tells me there is something wrong I really have to have a look and
yes I'm building using all the warnings and checks on. I even have all the
GNAT style checks activated.

I don't want a language that let me do the same thing with hundredth ways among
which there is only one safe path. I'm not a gamer, fiddling with a piece of
code hours to find the good way is not what I'm looking for :)
So if you like to be as much constrained as possible, I think VB is much
better for this. Or even Logo. Plus it provides a turtle to help you.

You are really trying hard to fail to understand. All this is a trade off. We
all need lot of power (what VB or Logo won't give to us) but the power must be
*controlled* and it must come with a well defined *semantic*.

Pascal.

--

--|------------------------------------------------------
--| Pascal Obry Team-Ada Member
--| 45, rue Gabriel Peri - 78114 Magny Les Hameaux FRANCE
--|------------------------------------------------------
--| http://www.obry.org
--| "The best way to travel is by means of imagination"
--|
--| gpg --keyserver wwwkeys.pgp.net --recv-key C1082595
 
I

Ioannis Vranos

REH said:
These will mostly fail on systems with alignment constraints (SPARC, PPC,
etc.).

Why?


--
Ioannis Vranos

http://www23.brinkster.com/noicys

[I am using 90 characters word-wrapping - (800/640) *72= 90 or better described as:
(800/640) *80 - 10 for quotation= 90. If someone finds it inconvenient, please let me know].
 
I

Ioannis Vranos

Pascal said:
But you can propose a easy path that is safer. In this case there is lot of
more chance than the programmer will choose it. Easier is always appreciated :)
And a language must make it harder to do unsafe thing, like deallocation on
Ada. You have a instanciate Unchecked_Deallocation. The meaning is clear and
nobody can do that without noticing.

As Ludovic I appreciate a lot the help of the compiler and never try to fight
it. If it tells me there is something wrong I really have to have a look and
yes I'm building using all the warnings and checks on. I even have all the
GNAT style checks activated.

I don't want a language that let me do the same thing with hundredth ways among
which there is only one safe path. I'm not a gamer, fiddling with a piece of
code hours to find the good way is not what I'm looking for :)




You are really trying hard to fail to understand. All this is a trade off. We
all need lot of power (what VB or Logo won't give to us) but the power must be
*controlled* and it must come with a well defined *semantic*.


Actually most compilers provide warnings for lots of stuff and provide an option to
display all warnings (e.g. /Wall), even comparison between signed and unsigned integers
generates a warning. And they can also treat all warnings as *errors* if you specify so.

However the default is to display warnings for anything that is not required by the
standard to be treated as an error, and compile it.


In most cases, one fixes *all* warnings. However there *are* some cases where the
programmer knows better.

C++ is enabling by default, which I suppose is the opposite direction of Ada, and that's
why I think we can't understand one another. :)


C++ being less restrictive as the default, doesn't mean a compiler does not generate lots
of warnings!



--
Ioannis Vranos

http://www23.brinkster.com/noicys

[I am using 90 characters word-wrapping - (800/640) *72= 90 or better described as:
(800/640) *80 - 10 for quotation= 90. If someone finds it inconvenient, please let me know].
 
I

Ioannis Vranos

Ioannis said:
Actually most compilers provide warnings for lots of stuff and provide
an option to display all warnings (e.g. /Wall), even comparison between
signed and unsigned integers generates a warning. And they can also
treat all warnings as *errors* if you specify so.

However the default is to display warnings for anything that is not
required by the standard to be treated as an error, and compile it.


In most cases, one fixes *all* warnings. However there *are* some cases
where the programmer knows better.

C++ is enabling by default, which I suppose is the opposite direction of
Ada, and that's why I think we can't understand one another. :)


C++ being less restrictive as the default, doesn't mean a compiler does
not generate lots of warnings!


An example. First compile with the default behaviour, then with all warnings turned on:

int main()
{
int i=0;

unsigned j=4;

j<i;
}


C:\c>g++ temp.cpp -o temp.exe

C:\c>g++ -Wall temp.cpp -o temp.exe
temp.cpp: In function `int main()':
temp.cpp:7: warning: comparison between signed and unsigned integer expressions
temp.cpp:7: warning: statement has no effect

C:\c>




--
Ioannis Vranos

http://www23.brinkster.com/noicys

[I am using 90 characters word-wrapping - (800/640) *72= 90 or better described as:
(800/640) *80 - 10 for quotation= 90. If someone finds it inconvenient, please let me know].
 
R

REH

Ioannis Vranos said:

Well, suppose your class contains a variable of type double, which on some
systems is 8 bytes and requires 8 byte alignment. The array you are
defining to hold the class is a character array, which only requires 1 byte
alignment. If it is created with a 1 byte alignment, when you create the
class in it using placement new, now the double is misaligned. When you
access it nasty things can happen. Intel processors can handled misaligned
variables, but take extra clock cycles to do so. On a SPARC or PowerPC, you
will throw a hardware exception (segmentation fault or machine check most
likely). A way around this problem is to either 1) make an array of
doubles, 2) union the array with a double, or 3) allocate from the heap.

REH
 
R

REH

Ioannis Vranos said:
In C++ you can always do:

#include <new>


class SomeClass
{
};


void somefunc()
{
unsigned char obj[sizeof(SomeClass)];

SomeClass *p= new(obj)SomeClass;

delete p;
}


as also:


#include <new>


class SomeClass
{
};


SomeClass *somefunc()
{
static unsigned char obj[sizeof(SomeClass)];

return new(obj)SomeClass;
}


int main()
{
SomeClass *p= somefunc();

delete p;
}


Here the object is created in the stack.
Another problem with this is you cannot use delete on a pointer returned
from placement new. You must instead call the destructor directly:
p->~SomeClass();
 
L

Larry Kilgallen

Well, suppose your class contains a variable of type double, which on some
systems is 8 bytes and requires 8 byte alignment. The array you are
defining to hold the class is a character array, which only requires 1 byte
alignment. If it is created with a 1 byte alignment, when you create the
class in it using placement new, now the double is misaligned. When you
access it nasty things can happen. Intel processors can handled misaligned
variables, but take extra clock cycles to do so. On a SPARC or PowerPC, you
will throw a hardware exception (segmentation fault or machine check most
likely). A way around this problem is to either 1) make an array of
doubles, 2) union the array with a double, or 3) allocate from the heap.

This can depend on the operating system.

On Alpha (and I presume on Itanium) VMS catches such exceptions and fixes
them up. It slows things down, but the result is still correct.
 
T

Tapio Kelloniemi

The question is not only about compiler warnings or errors. Ada (as a
language) has been designed so that it is possible for the
compiler to check many mistakes which may cause bad results at run time.
Ada also makes it easier for the user to notice this kind of errors.
For example:

procedure X is

type Metres is new Natural;
type Seconds is new Natural;

M : Metrses := 0;
S : Seconds := 10;
begin
if M < S then -- Error, < is not defined for these types
...
end if;
end X;

This is a bit more verbose than using pure int instead of Metres and
Seconds, but if I wanted a C++ compiler to check this kind of error, I'm
afread that the resulting C++ code would be much more verbose.

Such mistakes as using a pointer to nothing and writing past the array
bounds don't often happen in Ada.
An example. First compile with the default behaviour, then with all warnings tu
rned on:

int main()
{
int i=0;

unsigned j=4;

j<i;
}

procedure Temp is
I : Integer := 0;
J : Natural := 4;
begin
I < J;
end Temp;

Without any warnings:
# gnatmake temp
gcc -c temp.adb
temp.adb:5:05: missing ":="
gnatmake: "temp.adb" compilation error

Notice how the language prevents doing useless things.

If a replace I < J; with null; the result is:
gcc -c -gnatg temp.adb
temp.adb:2:04: warning: "I" is not modified, could be declared constant
temp.adb:2:04: warning: variable "I" is not referenced
temp.adb:3:04: warning: "J" is not modified, could be declared constant
temp.adb:3:04: warning: variable "J" is not referenced
gnatmake: "temp.adb" compilation error
 
I

Ioannis Vranos

REH said:
Well, suppose your class contains a variable of type double, which on some
systems is 8 bytes and requires 8 byte alignment. The array you are
defining to hold the class is a character array, which only requires 1 byte
alignment. If it is created with a 1 byte alignment, when you create the
class in it using placement new, now the double is misaligned.


But we can treat a double variable as a sequence of unsigned chars/plain chars plus copy
it byte by byte (char/unsigned char) to a new unsigned char/char sequence and have an
exact working copy of the original. So since we can do this for stand alone
doubles/sequences of doubles, then why can't we do this in the double contained in a class
case?
 
I

Ioannis Vranos

REH said:
Another problem with this is you cannot use delete on a pointer returned
from placement new. You must instead call the destructor directly:
p->~SomeClass();


However in TC++PL 3, on page 576, there is a version of delete for placement new and
delete[] for placement new[]:


"void *operator new(size_t, void *p) throw() { return p; } // placement (10.4.11)
void operator delete(void *p, void *) throw() { }
void *operator new[](size_t, void *p) throw() { return p; }
void operator delete[](void *p, void *) throw() { }"
 
R

REH

Ioannis Vranos said:
But we can treat a double variable as a sequence of unsigned chars/plain chars plus copy
it byte by byte (char/unsigned char) to a new unsigned char/char sequence and have an
exact working copy of the original. So since we can do this for stand alone
doubles/sequences of doubles, then why can't we do this in the double contained in a class
case?
I never said you couldn't. I was just pointing out that what you wrote was
not portable, and would not work for any arbitrary class. Besides, why
would you incur the extra time and complexity of manually moving the data
back and forth, byte-by-byte instead of just insuring the correct alignment
and letting the compiler take care of it?
 
R

REH

Ioannis Vranos said:
REH said:
Another problem with this is you cannot use delete on a pointer returned
from placement new. You must instead call the destructor directly:
p->~SomeClass();


However in TC++PL 3, on page 576, there is a version of delete for placement new and
delete[] for placement new[]:


"void *operator new(size_t, void *p) throw() { return p; } // placement (10.4.11)
void operator delete(void *p, void *) throw() { }
void *operator new[](size_t, void *p) throw() { return p; }
void operator delete[](void *p, void *) throw() { }"
OK, that's news to me. But how does the compiler know to call this version
of delete just from:

delete p;

?
 
I

Ioannis Vranos

REH said:
chars plus copy


and have an


contained in a class


I never said you couldn't.


What I meant, is that the above is a safe and portable operation to do. It is guaranteed
by the standard.

I was just pointing out that what you wrote was
not portable, and would not work for any arbitrary class. Besides, why
would you incur the extra time and complexity of manually moving the data
back and forth, byte-by-byte instead of just insuring the correct alignment
and letting the compiler take care of it?


This question can be rephrased to: "Why is memcpy() needed?".
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,202
Messages
2,571,057
Members
47,663
Latest member
josh5959

Latest Threads

Top