Copy Link
Add to Bookmark
Report

C++ Newsletter/Tutorial Issue 19

eZine's profile picture
Published in 
CPPNL
 · 7 Mar 2022

Issue #019
January, 1997

Contents

  • Placement New/Delete
  • Introduction to Exception Handling Part 5 - terminate() and unexpected()
  • Notes From ANSI/ISO - Recent Changes to terminate() and unexpected()
  • Introduction to STL Part 6 - Stacks

PLACEMENT NEW/DELETE

In C++, operators new/delete mostly replace the use of malloc() and free() in C. For example:

    class A { 
public:
A();
~A();
};

A* p = new A;

...

delete p;


allocates storage for an A object and arranges for its constructor to be called, later followed by invocation of the destructor and freeing of the storage. You can use the standard new/delete functions in the library, or define your own globally and/or on a per-class basis.

There's a variation on new/delete worth mentioning. It's possible to supply additional parameters to a new call, for example:

    A* p = new (a, b) A;


where a and b are arbitrary expressions; this is known as "placement new". For example, suppose that you have an object instance of a specialized class named Alloc that you want to pass to the new operator, so that new can control allocation according to the state of this object (that is, a specialized storage allocator):

    class Alloc {/* stuff */}; 

Alloc allocator;

...

class A {/* stuff */};

...

A* p = new (allocator) A;


If you do this, then you need to define your own new function, like this:

    void* operator new(size_t s, Alloc& a) 
{
// stuff
}


The first parameter is always of type "size_t" (typically unsigned int), and any additional parameters are then listed. In this example, the "a" instance of Alloc might be examined to determine what strategy to use to allocate space. A similar approach can be used for operator new[] used for arrays.

This feature has been around for a while. A relatively new feature that goes along with it is placement delete. If during object initialization as part of a placement new call, for example during constructor invocation on a class object instance, an exception is thrown, then a matching placement delete call is made, with the same arguments and values as to placement new. In the example above, a matching function would be:

    void operator delete(void* p, Alloc& a) 
{
// stuff
}


With new, the first parameter is always "size_t", and with delete, always "void*". So "matching" in this instance means all other parameters match. "a" would have the value as was passed to new earlier.

Here's a simple example:

    int flag = 0; 

typedef unsigned int size_t;

void operator delete(void* p, int i)
{
flag = 1;
}

void* operator new(size_t s, int i)
{
return new char[s];
}

class A {
public:
A() {throw -37;}
};

int main()
{
try {
A* p = new (1234) A;
}
catch (int i) {
}
if (flag == 0)
return 1;
else
return 0;
}


Placement delete may not be in your local C++ compiler as yet. In compilers without this feature, memory will leak. Note also that you can't call overloaded operator delete directly via the operator syntax; you'd have to code it as a regular function call.

INTRODUCTION TO EXCEPTION HANDLING PART 5 - TERMINATE() AND UNEXPECTED()

Suppose that you have a bit of exception handling usage, like this:

    void f() 
{
throw -37;
}

int main()
{
try {
f();
}
catch (char* s) {
}

return 0;
}


What will happen? An exception of type "int" is thrown, but there is no handler for it. In this case, a special function terminate() is called. terminate() is called whenever the exception handling mechanism cannot find a handler for a thrown exception. terminate() is also called in a couple of odd cases, for example when an exception occurs in the middle of throwing another exception.

terminate() is a library function which by default aborts the program. You can override terminate if you want:

    #include <iostream.h> 
#include <stdlib.h>

typedef void (*PFV)(void);

PFV set_terminate(PFV);

void t()
{
cerr << "terminate() called" << endl;
exit(1);
}

void f()
{
throw -37;
}

int main()
{
set_terminate(t);

try {
f();
}
catch (char* s) {
}

return 0;
}


Note that this area is in a state of flux as far as compiler adaptation of new features. For example, terminate() should really be "std::terminate()", and the declarations may be found in a header file "<exception>". But not all compilers have this yet, and these examples are written using an older no-longer-standard convention.

In a similar way, a call to the unexpected() function can be triggered by saying:

    #include <iostream.h> 
#include <stdlib.h>

typedef void (*PFV)(void);

PFV set_unexpected(PFV);

void u()
{
cerr << "unexpected() called" << endl;
exit(1);
}

void f() throw(char*)
{
throw -37;
}

int main()
{
set_unexpected(u);

try {
f();
}
catch (int i) {
}

return 0;
}


unexpected() is called when a function with an exception specification throws an exception of a type not listed in the exception specification for the function. In this example, f()'s exception specification is:

    throw(char*)


A function declaration without such a specification may throw any type of exception, and one with:

    throw()


is not allowed to throw exceptions at all. By default unexpected() calls terminate(), but in certain cases where the user has defined their own version of unexpected(), execution can continue.

There is also a brand-new library function:

    bool uncaught_exception();


that is true from the time after completion of the evaluation of the object to be thrown until completion of the initialization of the exception declaration in the matching handler. For example, this would be true during stack unwinding (see newsletter #017). If this function returns true, then you don't want to throw an exception, because doing so would cause terminate() to be called.

NOTES FROM ANSI/ISO - RECENT CHANGES TO TERMINATE() AND UNEXPECTED()

Jonathan Schilling, jls@sco.com

Earlier in this issue the basic purposes of the terminate() and unexpected() functions are described. In the past year the standards committee has made several refinements to these functions.

The committee has confirmed that direct calls may be made to these functions from application code. So for instance:

    #include <exception> 
...
if (something_is_really_wrong)
std::terminate();


This will terminate the program without unwinding the stack and destroying local (and finally static) objects. Alternatively, if you just throw an exception that doesn't get handled, it is implementation- dependent whether the stack is unwound before terminate() is called. (Most implementations will likely support a mode wherein the stack is not unwound, so that you can debug from the real point of failure).

Probably the main purpose of making direct calls to terminate() and unexpected() will be to simulate possible error conditions in application testing, especially when the application has established its own terminate and unexpected handlers.

The committee has changed slightly the definition of what handlers are used when terminate() or unexpected() are called. In most cases, they are now the handlers in effect at the time of the throw, which are not necessarily the current handlers. Usually they are one and the same, but consider:

    #include <exception> 

void u1() { ... }
void u2() { ... }

class A {
public:
A() { ... }
A(const A&) { ... std::set_unexpected(u2); }
};

void f() throw(int)
{
A a;
throw a; // which unexpected handler gets called?
}

int main()
{
std::set_unexpected(u1);
f();
return 0;
}


The copy constructor for A is called as part of the throw operation in f(), so by the time the C++ implementation determines that an unexpected handler needs to be called, u2() is the current handler. However, based on this recent change, it is the handler in effect at the time of the throw - u1() - which gets called. On the other hand, if a direct call to terminate() or unexpected() is made from the application, it is always the current handler which gets called.

Some would argue that this kind of rule just adds complexity without much benefit to already-complex C++ implementations, but others feel that if an application is going to be dynamically changing its terminate and unexpected handlers, retaining the correct association is important.

In the next issue we'll talk about another clarification of terminate() and unexpected(), this time related to the uncaught_exception() library function introduced above.


INTRODUCTION TO STL PART 6 - STACKS

We're nearly done discussing the basic data structures underlying the Standard Template Library. One more worth mentioning is stacks. In STL a stack is based on a vector, deque, or list. An example of stack usage is:

    #include <iostream> 
#include <stack>
#include <list>

using namespace std;

int main()
{
stack<int, list<int> > stk;

for (int i = 1; i <= 10; i++)
stk.push(i);

while (!stk.empty()) {
cout << stk.top() << endl;
stk.pop();
}

return 0;
}


We declare the stack, specifying the underlying type (int), and the sort of list used to represent the stack (list<int>).

We then use push() to push items on the stack, top() to retrieve the value of the top item on the stack, and pop() to pop items off the stack. empty() is used to determine whether the stack is empty or not.

We will move on to other aspects of STL in future issues. One data structure not discussed is queues and priority_queues. A queue is something like a stack, except that it's first-in-first-out instead of last-in-first-out.


ACKNOWLEDGEMENTS

Thanks to Nathan Myers, Eric Nagler, David Nelson, and Jonathan Schilling for help with proofreading.


SUBSCRIPTION INFORMATION / BACK ISSUES

To subscribe to the newsletter, send mail to majordomo@world.std.com with this line as its message body:

subscribe c_plus_plus

Back issues are available via FTP from:

rmi.net /pub2/glenm/newslett

or on the Web at:

http://rainbow.rmi.net/~glenm

There is also a Java newsletter. To subscribe to it, say:

subscribe java_letter

using the same majordomo@world.std.com address.

-------------------------

Copyright (c) 1997 Glen McCluskey. All Rights Reserved.

This newsletter may be further distributed provided that it is copied in its entirety, including the newsletter number at the top and the copyright and contact information at the bottom.

Glen McCluskey & Associates
Professional C++ Consulting
Internet: glenm@glenmccl.com
Phone: (800) 722-1613 or (970) 490-2462
Fax: (970) 490-2463
FTP: rmi.net /pub2/glenm/newslett (for back issues)
Web: http://rainbow.rmi.net/~glenm

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT