Copy Link
Add to Bookmark
Report

C++ Newsletter/Tutorial Issue 15

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

Issue #015
September, 1996

Contents

  • Introduction to Exception Handling Part 1 - A Simple Example
  • Testing C++ Compilers
  • Notes From ANSI/ISO - Extern Inlines By Default
  • Introduction to STL Part 2 - Vectors, Lists, Deques
  • Using C++ as a Better C Part 15 - Bit Field Types


INTRODUCTION TO EXCEPTION HANDLING PART 1 - A SIMPLE EXAMPLE

In this and subsequent issues we will be discussing some aspects of C++ exception handling. To start this discussion, let's consider a simple example. Suppose that you are writing a program to manipulate calendar dates, and want to check whether a given year is in the 20th century (ignoring the issue of whether the 21st century starts in 2000 or 2001!).

Using exceptions, one way to do this might be:

    #include <iostream.h> 

class DateException {
char* err;
public:
DateException(char* s) {err = s;}
void print() const {cerr << err << endl;}
};

// a function that operates on dates
void g(int date)
{
if (date < 1900)
throw DateException("date < 1900");
if (date > 1999)
throw DateException("date > 1999");
// process date ...
}

// some code that uses dates
void f()
{
g(1879);
}

int main()
{
try {
f();
}
catch (const DateException& de) {
de.print();
return 1;
}

return 0;
}


The basic idea here is that we have a try block:

    try { 
f();
}


Within this block, we execute some code, in this case a function call f(). Then we have a list of one or more handlers:

    catch (DateException de) { 
de.print();
return 1;
}


If an abnormal condition arises in the code, we can throw an exception:

    if (date < 1900) 
throw DateException("date < 1900");


and have it caught by one of the handlers at an outer level, that is, execution will continue at the point of the handler, with the execution stack unwound.

An exception may be a class object type such as DateException, or a fundamental C++ type like an integer. Obviously, a class object type can store and convey more information about the nature of the exception, as illustrated in this example. Saying:

    throw -37;


will indeed throw an exception, which may be caught somewhere, but this idiom is not particularly useful.

What if the handler we declare is changed slightly, as in:

    catch (DateException* de) { 
de->print();
return 1;
}


In this case, because an object of type DateException is thrown, rather than a DateException* (pointer), no corresponding handler will be found in the program. In that case, the runtime system that handles exception processing will call a special library function terminate(), and the program will abort. One way to avoid this problem is to say:

    main() 
{
try {
body_of_program();
}
catch (...) {
// all exceptions go through here
return 1;
}

return 0;
}


where "..." will catch any exception type.

We will explore various details of exception handling in future issues, but one general comment is in order. C++ exceptions are not the same as low-level hardware interrupts, nor are they the same as UNIX signals such as SIGTERM. And there's no linkage between exceptions such as divide by zero (which may be a low-level machine exception) and C++ exceptions.


TESTING C++ COMPILERS

As a consultant specializing in C++ and Java, one of the things I do is develop test suites for C++ compilers. I recently wrote a 4-page summary of the approach that is used to develop such suites. If you'd like a copy via electronic mail, please send me (glenm@glenmccl.com) your Internet address.


NOTES FROM ANSI/ISO - EXTERN INLINES BY DEFAULT

Jonathan Schilling, jls@sco.com

In the original C++ language, inline functions always had internal linkage. Then a change was made to allow the possibility of external linkage. Now a change has been made to make external linkage the default. This may alter the behavior of some existing code.

The linkage of a global inline function doesn't really matter if it is in fact inlined (unless it contains static local variables; see below), but in real life the inline specifier is sometimes not honored for one reason or another (see C++ Newsletter #007). When this happens, linkage matters. Consider the following:

file1.C:

    inline int f(int x, int y) { 
return 2 * x - y;
}

... f(3, 5) ...


file2.C:

    inline int f(int x, int y) { 
return 3 * x - y;
}

... f(3, 5) ...


That the two definitions of f() are different may be an accident or may be an intentional (but probably poor) coding practice. If the compiler does inline the function calls, the call to f() in file1 will return 1, while the apparently identical call to f() in file2 will return 4. If the functions are for some reason not inlined, but inline functions have internal linkage, then the f() calls will still return 1 and 4. This is because the compiler will generate a separate, local function body in each object file that has a call to the function, using the definition that is available for that compilation; no language rules violation occurs.

In 1994 the C++ ANSI/ISO committee made a change to the language to allow inline functions to be declared with the "extern" specifier, giving such functions external linkage

    extern inline int f(int x, int y) { ... }


but the default for global inline functions was still kept as internal. (Inline member functions get the linkage of the class they are declared in, which usually means external).

The meaning of "extern inline" is that if calls to a function are not generated inline, then a compiler should make just one copy of the definition of the function, to be shared across all object files. For example, if code in more than one object file takes the address of the above function f() and prints it out, the same address value should appear each time. Otherwise, extern inline functions are like static inline functions: the function definition is compiled multiple times, once for each source file that calls it.

Implementing this sharing requires additional linker support on some platforms, which may be part of the reason why "extern inline" is not yet supported in some C++ compilers. Additionally, this sharing does not always have to be done; if an inline function does not contain static local variables or have its address taken, there is no way to tell whether the definition is shared or not, and a compiler is free to not share it (at the cost of increasing program size). And there is even some compiler sleight-of-hand that can avoid sharing when these conditions are present.

But what happens if, as in the original example, an extern inline function that is not inlined has different definitions in the different places it is used?

In this case, there is a violation of C++'s One Definition Rule. This means that the program's behavior is considered undefined according to the language standard, but that neither the compiler nor the linker is required to give a diagnostic message. In practice, this means that, depending on how the implementation works, the compiler or linker may just silently pick one of the definitions to be used everywhere. For the above example, both of the calls to f() might return 1 or both might return 4.

But the 1994 change did not risk altering the meaning of any existing code, because an extern specifier would have to be added to the source to trigger these new semantics.

However, at the most recent standards meeting in Stockholm in July, a further change was made to make external linkage the default for non-member inline functions. (The immediate motivation for this change was a need of the new template compilation model that was adopted at the same meeting; but more generally it was felt that changing the default was an idea whose time had come, and the change was approved unanimously in both ANSI and ISO).

With this latest change, all non-member inline functions that do not explicitly specify "static" will become external, and thus it is possible that existing code will now function differently. To help cope with this, compilers may provide a compatibility option to give inline functions their old linkage. It is also possible for users to force the old behavior by use of the preprocessor

    #define inline static inline


but this only works if there are no member functions declared with "inline", and as a preprocessor-based solution is not recommended.

Note that change of behavior may occur even when there is a single source definition for a function. For example, assume that the following function is defined in a header file somewhere:

file3.h:

    inline int g(int x, int y) 
{
#ifndef NDEBUG
cerr << "I'm in g()" << endl;
#endif
if (x >= y)
return h(x, y);
else
return 2 * x - y;
}


Even though the source for the function is defined only once, the function can have different semantics depending upon where it is compiled. For example, in one file NDEBUG might be defined, but in another not. Or, the call to function h() might be overloaded and resolve differently in one file from another, depending upon what other functions were visible in each file. These cases are also violations of the One Definition Rule, and may lead to a change in behavior of existing code.

Still another way that existing inline function code can have its behavior altered is if it uses local static variables. Consider the following function e() defined in a header file:

    inline int e() { 
static int i = 0;
return ++i;
}


When the function previously had internal linkage, there was a separate "i" allocated within each object file that had a call to e(). But now that the function gets external linkage, there is only one copy of e(), and only one "i". This will cause calls to the function to return different values than before.

The One Definition Rule is a weakness of C++ where software reliability is concerned; languages with stronger module systems (such as Ada or Java) do not have these kinds of problems. As a general guideline, global inline functions should operate upon their arguments, and avoid static variables, interactions with the surrounding context, and the preprocessor.


INTRODUCTION TO STL PART 2 - VECTORS, LISTS, DEQUES

In the previous issue we introduced the C++ Standard Template Library. STL is a combination of containers used to store data, iterators on those containers, and algorithms to manipulate containers of data. STL uses templates and inline functions very heavily.

In this issue we'll talk about some of the types of containers that are available for holding data, namely vectors, lists, and deques.

A vector is like a smart array. You can use [] to efficiently access any element in the vector, and the vector grows on demand. But inserting into the middle of a vector is expensive, because elements must be moved down, and growing the vector is costly because it must be copied to another vector internally.

A list is like a doubly-linked list that you've used before. Insertion or splicing of subsequences is very efficient at any point in the list, and the list doesn't have to be copied out. But looking up an arbitrary element is slow.

A deque classically stands for "double-ended queue", but in STL means a combination of a vector and a list. Indexing of arbitrary elements is supported, as are list operations like efficiently popping the front item off a list.

To illustrate these notions, we will go through three examples. The first one is the same as given in the last newsletter issue, and shows how a vector might be used to store a list of 25 numbers and then shuffle them into random order:

    #include <vector> 
#include <algorithm>
#include <iostream>

using namespace std;

int main()
{
vector<int> v;

for (int i = 0; i < 25; i++)
v.push_back(i);

random_shuffle(v.begin(), v.end());

for (int j = 0; j < 25; j++)
cout << v[j] << " ";
cout << endl;

return 0;
}


With lists, we can't use [] to index the list, nor is random_shuffle() supported for lists. So we make do with:

    #include <list> 
#include <algorithm>
#include <iostream>

using namespace std;

int main()
{
list<int> v;

for (int i = 0; i < 25; i++)
v.push_back(i);

//random_shuffle(v.begin(), v.end());

for (int j = 0; j < 25; j++) {
cout << v.front() << " ";
v.pop_front();
}

cout << endl;

return 0;
}


where we add elements to the list, and then simply retrieve the element at the front of the list, print it, and pop it off the list.

Finally, we present a hybrid using deques. random_shuffle() can be used with these, because they have properties of vectors. But we can also use list operations like front() and pop_front():

    #include <algorithm> 
#include <iostream>
#include <deque>

using namespace std;

int main()
{
deque<int> v;

for (int i = 0; i < 25; i++)
v.push_back(i);

random_shuffle(v.begin(), v.end());

for (int j = 0; j < 25; j++) {
cout << v.front() << " ";
v.pop_front();
}

cout << endl;

return 0;
}


Which of vectors, lists, and deques you should use depend on the application, of course. There are several additional container types that we'll be looking at in future issues, including stacks and queues. It's also possible to define your own container types.

The performance of operations on these structures is defined in the standard, and can be relied upon when designing for portability.


USING C++ AS A BETTER C PART 15 - BIT FIELD TYPES

Here's a small difference between C and C++. In ANSI C, bit fields must be of type "int", "signed int", or "unsigned int". In C++, they may be of any integral type, for example:

    enum E {e1, e2, e3}; 

class A {
public:
int x : 5;
unsigned char y : 8;
E z : 5;
};


This extension was added in order to allow bit field values to be passed to functions expecting a particular type, for example:

    void f(E e) 
{
}

void g()
{
A a;
a.z = e3;
f(a.z);
}


Note that even with this relaxation of C rules, bit fields can be problematic to use. There are no pointers or references to bit fields in C++, and the layout and size of fields is tricky and not necessarily portable.

ACKNOWLEDGEMENTS

Thanks to Mike Anderson, Barb Howe, Nathan Myers, Eric Nagler, David Nelson, Terry Rudd, Jonathan Schilling, Elaine Siegel, and Clay Wilson 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:

rmii.com /pub2/glenm/newslett

or on the Web at:

http://www.rmii.com/~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) 1996 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: rmii.com /pub2/glenm/newslett (for back issues)
Web: http://www.rmii.com/~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