Documente Academic
Documente Profesional
Documente Cultură
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
References
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
References
ISO/IEC 14882:1998 Standard for the C++ Programming Language My notes from the 1999 Software Development conference:
http://dcserver/~dcharlap/sd99
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction To C++
Part 1: A better C than C
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
C Compatibility
Almost everything that is strict ANSI-C will compile under C++. Pre-ANSI language features may be incompatible. There are many new reserved words, including class, try, catch, template, namespace, public, private, protected, etc. GCC will automatically compile as C++ if the source file extension is .C, .cc, .cpp, .c++, .cp, or .cxx. A character constant is of type char, not int.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
In addition to C-style comments, C++ allows the use of a double-slash (//) to designate comments. Many C compilers support this, but this is was not part of standard C until the 1999 revision of the ISO standard (ISO/IEC 9899:1999)
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
In C, a function prototype declared with an empty argument list indicates an unspecified number of arguments. The keyword void is required to specify no arguments.
void foo(); // Unspecified arguments void foo(...); // Unspecified arguments void bar(void); // No arguments
In C++, an empty argument list indicates no arguments. The void keyword in this context is still supported for backward compatibility.
void foo(); // No arguments void foo(...); // Unspecified arguments void bar(void); // No arguments
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
It is no longer necessary to declare local variables at the top of a block. Declare them anywhere that makes the code easier to understand. A local variable's scope begins where it is declared and ends where its block ends. For example:
void foo() { printf("entering foo\n"); int i; i = rand(); printf ("Number is: %d\n", i); }
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Loop-Local Variables
Variables can even be declared as part of a loop or conditional construct. The variable's scope will end at the end of the construct. For example:
void foo() { for (int i=0; i<100; i++) { printf("i is now %d\n", i); } // The next line is an error. i is out of scope after the end of // the loop. printf("Final value: %d\n", i); }
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Loop-Local Variables
Note that the scoping of variables declared in a loop or conditional has changed from pre-standard versions of the language. Loop- and conditional-declared variables used to exist until the end of the block containing the construct (as if the variable was declared immediately before the start of the construct.) This is not true anymore! Unfortunately, this discrepancy can result in code that is not portable to pre-standard C++ compilers. If this is a concern, you must declare variables separately from loop and conditional constructs.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
10
Loop-Local Variables
void foo() { for (int i=0; i<100; i++) { printf("i is now %d\n", i); } printf("Final value: %d\n", i); }
This is a workaround:
void foo() { int i; for (i=0; i<100; i++) { printf("i is now %d\n", i); } printf("Final value: %d\n", i); }
11
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
It is legal for the same name to be declared in two different enumerated types, even with different values. When this is done, the enum's name must be specified at the point of usage, in order to resolve the ambiguity. For example:
12
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
A function may be defined with default values in the prototype. For example:
When called, any missing parameters are filled in with the defaults. For example, these pairs of calls are all identical:
10, 15, 20); 17); 17, 15, 20); 9, 87); 9, 87, 20);
13
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
All arguments with defaults must come after all arguments that do not have defaults:
int f(int, int=0, char* =0); // OK int g(int=0, int=0, char*); // error int h(int=0, int, char* =0); // error
Note that the space after the * in the third argument is significant. Otherwise it will get parsed as the *= operator, which is a syntax error.
14
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
A default argument can not be repeated or changed in subsequent declarations in the same scope:
void f(int x=7); void f(int=7); // error - repeated default void f(int=8); // error - changed default void g() { void f(int x=9); // OK, but hides outer declaration ... }
Declaring the name in a nested scope to hide an outer declaration can lead to all kinds of hard-to-debug errors. Don't do this if you value your sanity!
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
15
Inline Functions
Inline functions are a core language feature, not a compiler-specific extension. For obvious reasons, the inline function definition must appear in every file in which it's used. It is therefore customary to declare all inline functions in the same header file with the function prototype.
16
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
References
C++ introduces the concept of references. References behave similar to pointers, but they are accessed like the objects that they refer to. A reference is denoted by an & character A reference can never be NULL - meaning reference variables must always be initialized.
17
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
References
Most people consider the version using references easier to read and understand. It is also more popular among C++ programmers.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
18
References
In general, it is preferable to use references instead of pointers whenever possible. Some cases where pointers are necessary are:
When interfacing with C code When a function is returning dynamically-allocated data When support for a NULL object/pointer is required
19
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Function Overloading
The types of the argument list of a C++ function is part of it's name. These prototypes define three different functions:
ip_addr_t make_ip_address(u_int32 addr); ip_addr_t make_ip_address(u_int8 octet_list[4]); ip_addr_t make_ip_address(u_int8 octet1, u_int8 octet2, u_int8 octet3, u_int8 octet4);
When used, the provided argument types will tell the compiler which function to call.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
20
Function Overloading
In order to avoid confusion, two functions with the same name but different arguments should perform the same function. Functions can not be overloaded on their return types. This is illegal:
21
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Function Overloading
Function overloading can make errors in prototypes hard to track down. For instance, in SMF, if you accidentally define a persistent table using:
smdb_error foo_table::notify_imp();
Instead of:
There will be no error. You will have defined a different function which will not be called. This will probably result in unexpected behavior.
22
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Calling Convention
In order to implement function overloading (and other C++ language features), the calling convention is changed. C code can not call C++ functions because of this. To override this and use C calling conventions, declare the prototype "extern C" this way:
Or this way:
23
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Calling Convention
Note that use of extern "C" only changes the calling convention. This places some restrictions on the function (for example, you can't overload it), but it is still compiled with the C++ language. You can still use all C++ language features in the function's implementation.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
24
While malloc() and free() still exist, their use in C++ is discouraged. Instead, use the new and delete operators:
Note that new and delete are not compatible with malloc() and free(). Do not delete what you allocate with malloc(), and do not free() what you allocate with new.
25
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
The new and delete operators may also be used to allocate arrays of objects. For example:
Note the syntax change for delete. This is a requirement to prevent unwanted side effects involving destructors (to be discussed later.)
26
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
In C++, casting is discouraged. The rules of assignment compatibility and object inheritance (to be discussed later) make most casts unnecessary. Where casts are necessary, there are four types of casts:
Dynamic casts are part of run-time type identification and will be discussed later.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
27
Static Cast
The static_cast is similar to a C-style cast. It behaves almost identically to a C-style cast, but the types have to be somewhat compatible. (You can't for instance, use it to cast an integer onto a pointer.) Instead of writing:
You write:
28
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Reinterpret Cast
Sometimes you need to perform a cast that static_cast can't handle. For example, you may need to access something at a specific memory location, or you may be interfacing with a library that returns a pointer in an integer variable. To do this, there is reinterpret_cast, which tries to produce a value of the new type with a bit pattern as close as possible to the source type. For example:
int pointerValue = 0x12345678; int *p = reinterpret_cast<int *>(pointerValue); unsigned char *video_buffer = reinterpret_cast<unsigned char *>(0xb8000);
29
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Const Cast
Sometimes you have a pointer to a const object and you need to get a pointer to a non-const object from it. To do this, use const_cast:
Note that this only changes the pointer type, it does not change the object that is pointed to. If the object is const (and not just the pointer), casting away the const attribute may lead to undefined behavior.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
30
Casting in C++
In general, there is little need for casting in C++. most of the situations where casts were required in C have language constructs that will do the same thing more cleanly in C++. One place where casting is necessary is when casting from a void pointer onto a pointer to an actual type. This won't compile in C++:
31
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
New Casts
If you must cast, using the new casts will allow the compiler to better optimize the expression. Where possible, use static_cast or const_cast and avoid reinterpret_cast. Use of the new casts instead of C-style casts also makes it easier to search for casts in code. It is nearly impossible to write a regular expression to reliably search for C-style cast expressions.
32
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Constructing a value of type T from value e can be expressed as T(e). For example:
33
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Header inclusion Conditional compilation Inserting code fragments Inserting code blocks Dynamic generation of function code Defining constants, types and variables
For the first three, the preprocessor must still be used For the rest, C++ language constructs exist which are easier to understand and are less prone to unexpected side-effects.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
34
#define LOAD_2BYTE(PTR,VAR) { \ (VAR) = (*(PTR)++) << 8; \ (VAR) |= (*(PTR)++); } typedef unsigned char *puchar; inline void LOAD_2BYTE(puchar &ptr, u_int32 &var) { var = (*ptr++) << 8; var |= *ptr++; }
Do this:
35
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Another common place is when defining function-like macros, where the argument types are not known in advance. Templates (to be covered later) may be used to provide this functionality. For example, this:
36
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
This version:
template<class T> inline T min(T a, T b) { return ((a<b) ? a : b); } The template will make sure that a and b are compatible types (resulting in compiler errors if they are not.) The problem of macro arguments being evaluated multiple times (resulting in multiple-execution of side-effects) doesn't occur.
37
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
For places where the preprocessor is used to define constants, use either an enum or a const int. Instead of these:
#define CTYPE_HOP_IP4 (1) #define CTYPE_HOP_IP6 (2) #define FILL_PATTERN (0xdeadbeef) enum ctype_hop { ip4 = 1, ip6 = 2 }; const u_int32 fill_pattern = 0xdeadbeef;
Use these:
38
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
The mechanism for declaring const variables is much more optimized in C++. If the value of a const variable is known at compile time and is known not to change, the compiler does not typically assign storage for that variable. For example:
In these examples, c1 may not have storage assigned to it. c2 will, because p points to it, c3 and c4 will because the values are not known at compile time.
39
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
\ \ \ \ \
40
template <int i> struct mystruct { char *foo; int bar[i]; }; 2004. The Copyright in this document belongs to Marconi and no part of
this document should be used or copied without their prior written permission.
Mutable
Many times, you have a struct where you always want to be able to write to a specific field, even when the struct is declared const. In C, you have to either not declare it const, or explicitly cast away const when accessing the field. Both of these are bad. Not making the struct const will not allow the compiler to catch attempts to write to the struct. Explicitly casting away const will not allow the compiler to catch mistakes if the wrong field is accessed. These also prevent the compiler from making optimizations that depend on the const-nature of the struct.
41
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Mutable
C++ introduces the mutable keyword to work around this. For example:
A mutable field is always writable, even when the struct is const. For example:
const foo f = {1, 2, 3, 4}; f.c = 7; // OK - foo.c is mutable f.d = 9; // error - f is still const
42
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction to C++
Part 2: New Language Concepts
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Introduction to C++
Structs and classes
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Structs
In C++, the definition of a struct has changed. In addition to storing data ("data members") in a struct, you can also associate functions ("member functions") with a struct. A member function is also known as a method. A method's name need only be unique within the context of the struct.
45
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Structs
It is common and often useful for members of different classes to have the same name. Methods are called through an instance of, or pointer to, or reference to the struct. The act of bundling together data and methods designed to act on that data is known as encapsulation. Memory allocated to contain a struct's data is often referred to as an object or as an instance of the struct.
46
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Structs
struct ip_addr { u_int32 addr; // Host order u_int32 u_int32 void void }; network_order(); host_order(); set_network_order(u_int32); set_host_order(u_int32);
47
Structs
The methods are defined like ordinary functions, but with the :: operator to indicate the struct they belong to:
u_int32 ip_addr::network_order() { return htonl(addr); } u_int32 ip_addr::host_order() { return addr; } void ip_addr::set_network_order(u_int32 newaddr) { addr = ntohl(newaddr); } void ip_addr::set_host_order(u_int32 newaddr) { addr = newaddr; }
48
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Structs
Methods are called by dereferencing the struct using the same "." and "->" operators that are used for accessing data members.
49
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Structs
struct ip_addr { u_int32 addr; u_int32 network_order() { return htonl(addr); } u_int32 host_order() { return addr; } ... };
When declared this way, the methods will be inline when they are used.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
50
Structs
Methods can access the data members of their struct without using a pointer to the struct. The memory used is the struct that the method is called from:
51
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Structs
If a method actually needs a pointer to the struct it's working on, it is available. It is always named this. These two implementations are identical:
Most of the time, there will be no need to explicitly use the this pointer.
52
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Structs
Note that methods may access all members, even ones defined afterwards in the struct. This is perfectly legal (but a little confusing):
struct ip_addr { u_int32 network_order() { return htonl(addr); } u_int32 host_order() {return addr; } ... u_int32 addr; }
53
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Structs
In order to avoid the confusion of seeing a member used before it is declared in the file, you can declare inline methods after the struct in the header file:
struct ip_addr { u_int32 network_order(); u_int32 host_order(); ... u_int32 addr; } inline u_int32 ip_addr::network_order() { return htonl(addr); } inline u_int32 ip_addr::host_order() { return addr; }
54
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
};
foo *f = new foo; foo *g = new foo; f->a f->b g->a g->b = = = = 1; 2; 3; 4;
55
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Const Methods
A method may be declared const. This means the method may not modify any data members in the struct. For example:
struct ip_addr { u_int32 addr; u_int32 network_order() const { return htonl(addr); } u_int32 host_order() const { return addr; } ... };
56
Data Protection
With the previous example, the data member addr may be accessed without the accessor member functions. To prevent that, we can declare it private:
57
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Data Protection
Private members (data and functions) may only be accessed from that struct's methods. Public members may be accessed by anything.
58
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Data Protection
This means that with the following struct, we can't call the accessors methods from code declared outside the struct:
struct ip_addr { private: u_int32 addr; u_int32 u_int32 void void }; network_order(); host_order(); set_network_order(u_int32 newaddr); set_host_order(u_int32 newaddr);
59
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Data Protection
struct ip_addr { private: u_int32 addr; public: u_int32 u_int32 void void };
60
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Data Protection
It is very common to declare data members private. This helps to enforce encapsulation (forcing all access to go through accessor methods.) Because of this, the keyword class was invented. A class is a struct, where members are private by default instead of public.
61
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Data Protection
There is no difference between a class and a struct other than the default protection. This is exactly the same as our previous example:
62
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Data Protection
In addition to public and private, there is a third kind of protection, called protected. A protected member may be accessed by methods of that class, and by methods of subclasses, but not by methods of unrelated classes.
63
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Friends
Sometimes, the data protection keywords are insufficient. For example, imagine classes that describe a Vector (an array of floats) and a Matrix (an array of Vectors):
64
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Friends
Vector multiply(const Matrix &m, const Vector &v) { Vector r; for (int i=0; i<4; i++) { r.v[i] = 0; for (int j=0; j<4; j++) r.v[i] += m.v[i].v[j] * v.v[j]; } return r; }
65
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Friends
As we can see, this multiply function requires direct access to the data members of both the Vector and Matrix classes. We could just make those members public, but that would allow all other code to have access. We could make the function a method on one of the classes, but that would not allow us access to the other class's data, and one function can't be a method of two classes.
66
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Friends
The solution here is to declare the multiply function to be a friend of the two classes as a part of the class definition.
class Matrix; // Forward declaration class Vector { float v[4]; friend Vector multiply(const const }; class Matrix { Vector v[4]; friend Vector multiply(const const };
Matrix&, Vector&);
Matrix&, Vector&);
67
A friend can access private members of the class that has declared it a friend.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Friends
You can also declare an entire class to be a friend of another class (not just a function):
class foo; // Definition is unimportant class bar { // Definition is unimportant friend class foo; };
68
Friends
Although it is sometimes necessary to declare methods and classes as friends, it usually is not. If you find yourself having to declare a lot of friends, then you probably have a design error in your program. Go back and think about what changes you can make to your classes that may make the friends unnecessary.
69
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Constructors
A method whose name is the same as the class name is a constructor. It is automatically executed when the object is allocated. Constructors have no return type. Constructors should be public, since objects are normally created by code that's not part of the class.
70
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Constructors
This will print out "0x12345678" The value is assigned by the constructor which automatically executed when the new operator was called.
71
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Constructors
class ip_addr { u_int32 addr; public: ip_addr() { addr ip_addr(u_int32 newaddr) { addr ip_addr(ip_addr& newaddr) { addr ip_addr(ip_addr* newaddr) { addr };
= = = =
72
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Constructors
*a *b *c *d
= = = =
They're also used for creating objects as local variables and temporary variables:
73
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Constructors
74
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Explicit Constructors
By default, a single-argument constructor also implies a conversion from that type during initialization. For some cases, this is exactly what you want. For example:
complex z = 2;
75
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Explicit Constructors
In other cases, this is not at all what you want. For example:
class string { char *s; public: string(int n) { s = new char[n+1]; } string(char *news) { s = new char[strlen(news)+1]; strcpy(s, news); }
} string s = 'a';
76
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Explicit Constructors
class string { char *s; public: explicit string(int n); string(char *news); }
77
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Explicit Constructors
With the explicit keyword on the integer constructor, we get the following results:
s1 = 'a'; // Error - this constructor is explicit s2 = 10; // Error - it's the same constructor s3(10); // OK - this is an explicit construction s4 = string(10); // OK s5 = "Foo"; // OK s6("Bar"); // OK
78
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Destructors
A destructor is a method that is automatically executed when an object is deleted. Its name is the name of the class, prefixed with a tilde (~). Destructors typically free any dynamically-allocated objects that are associated with the object being freed.
79
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
When you use the array-allocate version of new and delete, the constructor and destructor will be called for every element of the array. Constructors and destructors are run for all objects, no matter how they are created or destroyed. This includes dynamic (heap) allocation, static, global or temporary. Watch out for unanticipated side effects from temporary object instantiation.
80
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Constructors for all objects will run as soon as the object is allocated, even if it is not allocated with the new operator. For objects declared globally, this will happen before the first line of main()! Globally-declared objects in a single source file are constructed in the order they appear in the file. Globallydeclared objects declared in separate source files are not constructed in a deterministic order. For static objects, the constructor will run the first time the object comes into scope.
81
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Destructors for all objects will run when the object is freed, even if this is due to it going out of scope. Objects that are freed simultaneously (like local variables that go out of scope) are destroyed in the reverse order of their construction. For static and global objects, this may not be deterministic, since the order of construction is not entirely deterministic. The time of destruction for global and static objects is not defined.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
82
When used as local variables, all object constructors will run when the object enters scope, in the order they are declared, and destructors will run when the object leaves scope. Destructors run in the opposite order from the constructors. For example: void f(int i) aa, bb and dd are constructed { (in that order) whenever f() is Table aa; called. If i>0, cc will be Table bb; constructed and destroyed if (i>0) before dd is constructed. Before { Table cc; exiting, dd, bb and aa will be } destroyed (in that order)
Table dd; }
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
83
When objects are assigned to each other, a memberwise copy is made (similar to how C structs are assigned to each other.) This is often not what you want:
// initialization
// assignment
t2 and t3 will be clones of t1, including any internal pointers. You probably really wanted a new object containing the same data.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
84
A copy constructor and an overload of the assignment operator can solve this problem:
The copy constructor is used during initialization. The assignment operator overload is used for assignment. (Operator overloading will be discussed later.) Implementations may then do what's appropriate for properly copying the object's data.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
85
When overloading assignment, don't forget to consider the possibility of self-assignment (e.g. a=a;). You probably want to make the assignment a no-op when this happens:
Table& Table::operator=(const Table& t) { if (this != &t) { // do the data copying here } return *this; }
86
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
If you allocate an object with malloc(), the constructors will not execute. Internal housekeeping data for handling virtual methods and related things will not be initialized. Similarly, destructors will not execute if memory is freed using free(). In other words, don't do this. Don't use malloc() and free() unless the object has no methods and will be allocated or freed by C code. Otherwise, use new and delete.
87
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
For an example of constructors and destructors, here's a struct containing an array of an unknown number of IP addresses. The array is allocated by the constructor. It is freed by the destructor. Note that this will use the array-allocation version of the new and delete operators.
88
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
class addr_list { int count; u_int32 *addrs; public: addr_list(int newcount) { count = newcount; addrs = new u_int32[newcount]; } ~addr_list() { if (addrs != NULL) delete[] addrs; }
...
}
89
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Unions
A named union is defined as a struct, where every member has the same address. A union may have member functions, but no static members. Because the compiler can't know which member is in use at any given time, no union members may have constructors or destructors.
90
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Subclasses
It is possible to define an object class in terms of differences from another object. This is known as inheritance. For instance, an IP header with options is an ordinary IP header with an extra field.
91
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
IP Header example
Here's an IP header:
struct ip_header { u_int8 version; u_int8 hdr_length; u_int8 tos; u_int16 payload_len; u_int16 packet_id; u_int16 fragmentation_info; u_int8 ttl; u_int8 l4pid; u_int16 checksum; u_int32 source_addr; u_int32 dest_addr; };
92
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
IP Header example
93
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Subclasses
A pointer to a subclass is assignment compatible with a pointer its parent (or grandparent, etc.) class. This is perfectly legal:
94
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Methods (including constructors and destructors) are inherited just like data members:
struct ip_header { ... u_int32 get_source_addr() { return source_addr; } } ip_header_with_options *h; u_int32 addr = h->get_source_addr();
95
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Method Overrides
It is possible for a class and a subclass to define methods with the same name:
struct ip_header {
...
void printsomething() { printf("ip_header_with_options\n"); } };
96
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Method Overrides
When a class and a subclass implement the same method, the one that is called depends on the type of the pointer or reference used:
ip_header_with_options h; ip_header *hh = &h; // This prints "ip_header_with_options" h.printsomething(); // This prints "ip_header" hh->printsomething();
97
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Virtual Methods
Most of the time, however, it is more useful to execute the method that corresponds with the class of the data instead of the class of the pointer. This can be done by declaring the method virtual.
98
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Virtual Methods
struct ip_header {
...
virtual void printsomething() { printf("ip_header_with_options\n"); } }
99
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Virtual Methods
ip_header_with_options h; ip_header *hh = &h; // This prints "ip_header_with_options" h.printsomething(); // This also prints "ip_header_with_options" hh->printsomething();
100
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Virtual Methods
Once a method is declared virtual, subclasses do not need to continue using the virtual keyword. The virtual nature of the method is inherited and can not be removed by subclasses. Nevertheless, it is a good idea to continue using it, because the resulting code is easier to understand.
101
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Sometimes, you may have to define a class that will never be instantiated, in order for two related classes to share a common parent. In order to prohibit instantiation of such classes, you can declare a virtual method pure virtual, by suffixing "=0" to the declaration.
102
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
For example, imagine a class representing an interface. You have two subclasses - numbered and unnumbered. Because they share common methods, they must share a common superclass. But you don't want any code allocating instances of the superclass (the undifferentiated interface.)
103
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
class interface { int index; public: virtual int get_index() { return index; } virtual u_int32 get_addr() = 0; virtual u_int32 get_ifid() = 0; };
104
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
class numbered_interface : public interface { u_int32 addr; public: virtual u_int32 get_addr() { return addr; } virtual u_int32 get_ifid() { return 0; } }
105
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
class unnumbered_interface: public interface { u_int32 ifid; public: virtual u_int32 get_addr() { return get_router_addr(); } virtual u_int32 get_ifid() { return ifid; } }
106
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
With this example, any attempt to instantiate an interface will result in a compile-time error. There is no problem instantiating either subclass. They can still be assigned to pointers to interface, and the virtual methods can still be called through those pointers. For example:
interface *i, *j, *k; i = new interface; // Illegal - pure virtual j = new numbered_interface; // OK k = new unnumbered_interface; // OK
107
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
A pure virtual method is inherited, just like all other methods. You don't have to override all pure virtual methods, but if you don't, your subclass will also be pure virtual, and you won't be able to instantiate it.
108
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Sometimes, a method must call a method of the parent class. Simply calling the method by name will call the local class version. To get a parent class version, you must name the class, using the :: operator.
109
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
struct ip_header { ... virtual void dump(); }; struct ip_header_with_options : public ip_header { ... virtual void dump(); };
110
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
...
printf("destination address: 0x%x\n", dest_addr); }
void ip_header_with_options() { ip_header::dump(); printf("options length: %d\n", options_length); for (int i=0; i<options_length; i++) printf("options[%d]: %d\n", i, options[i]); }
111
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
When constructors run, the superclass constructor always executes before the subclass constructor. So how do you pass parameters to the superclass constructor? The answer is you specify it using the : operator as part of the method's implementation header.
112
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
For example, here is an ip_header constructor that allows you to initialize the source and destination addresses, and a default constructor:
struct ip_header { ... ip_header() {}; ip_header(u_int32 src, u_int32 dst) { source_addr = src; dest_addr = dst; } }
113
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
In order for ip_header_with_options to call the nondefault superclass constructor, we declare its constructor this way:
struct ip_header_with_options : public ip_header { ... ip_header_with_options(u_int32 src, u_int32 dst) : ip_header(src, dst) {}; }
114
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
It's worth noting that ordinary variables can be intialized using the same technique, since basic variables can be initialized with "constructor syntax":
struct ip_header { ... ip_header() {}; ip_header(u_int32 src, u_int32 dst) : source_addr(src), dest_addr(dst) {} }
The main difference is that this example uses initialization and the previous example uses assignment. For basic non-const types, there is little difference between the two.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
115
Virtual Destructors
As you may have figured out by now, the destructor that is called when the delete operator is used should be based on what the data is, and not what the pointer is. Because of this, you should always declare your destructor methods virtual:
116
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Static Methods
Like data members, methods may be declared static. A static method is not really a method of the class. When it is called, it does not have a this pointer, and therefore can't access an object's members without an explicit pointer to the object. The difference between a static method and a nonmember function is that a static method's name is associated with a class. All the usual protection and scoping rules still apply.
117
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Static Methods
This may be convenient when building libraries. Especially when declaring functions that are closely related to a class, but don't actually act on objects of that class. Static methods may be called using an object pointer or reference (like normal methods.) They may also be called by explicitly naming the class. A method may not be both static and virtual.
118
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Static Methods
For example, let's add a static method to the ip_address class for identifying the class:
119
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Inheritance Protection
So far, we've always used the public keyword when one class inherits from another class. It is also possible to use private or protected in inheritance:
foo; bar1 : public foo; bar2 : protected foo; bar3 : private foo;
This keyword, when used in this capacity, is known as an access specifier because it specifies who may access the base class's members.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
120
Public Inheritance
When a derived class (D) inherits from a base class (B) with the public access specifier (the most common way):
All code may convert a D* pointer to a B* pointer without a cast. All code can access public members inherited from B. Only friends and members of D and friends and members of classes that inherit from D can access protected members inherited from B.
121
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Protected/Private Inheritance
When a derived class (D) inherits from a base class (B) with the protected access specifier:
Only friends and members of D and friends and members of classes that inherit from D can convert a D* pointer to a B* pointer without a cast. Only friends and members of D and friends and members of classes that inherit from D can access protected and public members inherited from B.
When a derived class (D) inherits from a base class (B) with the private access specifier:
Only friends and members of D can convert a D* pointer to a B* pointer without a cast. Only friends and members of D can access protected and public members inherited from B.
122
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Inheritance Protection
A class inherits with a default access specifier of private A struct inherits with a default access specifier of public
Regardless, you should always provide your own access specifier whenever you derive subclasses, because it makes the code easier to understand. And most of the time, the default (private inheritance of classes) is not what you want.
123
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction To C++
Exceptions
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Exceptions
It is common for functions to return error codes. In order to be safe, every time such a function is called, you must check the return code for an error. If the caller can't handle the error, it must pass the error to the calling function. This can be awkward. Especially if the code that handles the error is many calls removed from the code that generates the error.
125
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Exceptions
Exceptions solve this problem When an exceptional condition (like an error) is detected, the code will generate (aka "raise" or "throw") an exception. The exception will repeatedly pop (unwind) the call stack until an exception handler is found. If no handler is found, the program will abort.
126
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Exceptions in C
In C, you can accomplish similar behavior through careful use of the setjmp() and longjmp() functions. In C++, however, this won't work, because longjmp() doesn't call destructors on all the automatic objects that get freed by the popped stack frames. Exceptions will call destructors on automatic objects while the stack unwinds.
127
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Exceptions are generated with the throw keyword. Any kind of object may be thrown as an exception. For example:
128
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Exceptions are handled using blocks defined by the try and catch keywords. To catch an exception, wrap the potential exceptiongenerating code in a try block and put the exception handler in a catch block:
129
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Catching Exceptions
You
try { do_something(); } catch (int i) { printf("Caught int exception %d\n", i); } catch (vector v) { printf("Caught vector exception %d,%d\n", v.dx(), v.dy()); }
130
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Catching Exceptions
If the type of the exception thrown doesn't match what you're catching, then you won't catch it. The exception will continue to propagate up the call stack. If you must be able to catch an unknown exception type, you can do it by specifying an ellipsis for the catch argument.
131
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Catching Exceptions
Assignment compatibility still applies. If your code is written to catch a class, and a subclass (that was derived with a public access specifier) of that class is thrown, you will catch it. This allows you to define a hierarchy of exception classes for catching errors at various levels of granularity. The standard C++ library defines and uses a hierarchy of many such exceptions.
132
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Standard Exceptions
The standard C++ library makes extensive use of exceptions to indicate errors of various kinds. The standard exceptions are defined as a class hierarchy:
exception
logic_error
runtime_error
133
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Re-throwing Exceptions
Sometimes, you can't completely handle an exception. You want to handle what you can, and then let the exception continue propagating up the stack. The throw keyword, when used without any parameters from within a catch block will do this:
try { do_something(); } catch (...) // catch everything { cleanup_some_state(); throw; // re-throw the exception }
134
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction To C++
Part 3: Advanced Concepts
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Introduction To C++
Operator Overloading
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Operator Overloading
All the standard operators may be overloaded just like functions. When used properly, this can make your code easier to understand. It can also be abused, making code an unreadable mess.
137
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
As an example of where operator overloading may be useful, imagine a class for a two-dimensional vector:
class vector { double deltax, deltay; public: vector(double newdx, double newdy) { deltax=newdx; deltay=newdy; } void set_dx(double newdx) { deltax = newdx; } void set_dy(double newdy) { deltay = newdy; } double dx() { return deltax; } double dy() { return deltay; } }
138
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Now, suppose we want to add two vectors together. We could write a function to do it:
vector add_vectors(vector &v1, vector &v2) { return vector(v1.dx() + v2.dx(), v1.dy() + v2.dy()); }
139
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
vector operator+(vector &v1, vector &v2) { return vector(v1.dx() + v2.dx(), v1.dy() + v2.dy()); }
vector v1(1,5); vector v2(10,3); vector v3(0,0); v3 = v1 + v2; // v3 is now {dx=11, dy=8}
140
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Most (but not all) operator overloads can also be implemented as a method on the class. The previous operator overload is the same as this:
class vector { ... vector operator+(vector &v) { return vector(deltax + v.deltax, deltay + v.deltay); } }
141
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Rules of Overloading
Most (but not all) operators may be overloaded New operators may not be defined The number of arguments to an operator may not be changed Precedence rules may not be changed The same operator may be overloaded many times, with different argument types (just like functions)
142
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Operator Overloading
Assignment (=) Comparison (==, !=, <=, >=, >, <) Arithmetic (+, -, *, /, %, +=, -=, *=, /=, %=, ++, --) (for numeric classes) Boolean (|, &, ^, <<, >>, |=, &=, ^=, <<=, >>=, ||, &&) (for Boolean and bit-field classes) Streaming I/O (<<, >>) (when applied to iostream classes) Concatenation (+, +=) (when applied to sequences)
143
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
PLEASE don't go overboard. Before you decide to define an operator overload, think carefully what code using your overload will look like. If the meaning isn't intuitively obvious, use an ordinary function or method call instead. Many new programmers, after learning about operator overloading, like to use it all over the place. More often than not, the resulting code is impossible to understand. Well thought out operator overloads can make code easy to understand. Overloads that are not well thought out can make code very difficult to understand.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
144
Introduction To C++
Templates
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
class list_node { list_node *next, *prev; public: list_node *get_next() { return next; } list_node *get_prev() { return prev; } void insert_before(list_node *node) { node->prev=prev; node->next=this; prev->next=node; prev=node; } }
146
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
But an empty list node like this is not normally useful. We usually want to keep a list of some kind of data. One solution (which is commonly done in the RCI code) is to put a list node into a larger struct and then do some pointer manipulation in order to access the data (outer struct) from a pointer to the list node (inner struct). There are two big problems with this approach:
It is difficult to understand the code when reading it later. It requires explicit casting and pointer arithmetic, both of which can be sources of errors that the compiler can't catch.
147
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Another solution is to put a (void *) pointer in the list_node class, and have it point to the data. This is easier to understand, but it still has many problems:
We must take steps to make sure that the data objects and the list nodes don't get out of sync with each other. We are still being forced to cast pointers in order to access the data. The compiler can't protect us against mistakes if we cast incorrectly. The pointer can be NULL There are now two objects that must be allocated and freed together.
148
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
A third solution is to make lots of variant list_node classes that contain the appropriate data.
This is a lot of maintenance. If we change our implementation, we've got a lot of classes to change. This can introduce errors - especially the cut-and-paste kind that take a long time to clean up. We (probably) have to declare these classes separately from the places where we're using them. In a large project, it will be easy to forget which ones we need and which ones we don't need. Cleaning up no-longer-used list class variants can be time consuming. Every time we need to change the data portion of a list, we must change or create a class variant. This can be laborintensive and may introduce errors.
149
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
A fourth solution is to make lots of variant list_node classes that are all subclasses of a common list_node class. This solves some of the variant-classes problems, but still doesn't solve them all.
The classes still will be declared separately from usage We still have maintenance issues if we change the data of a list. We also end up introducing an inheritance relationship between different list-node classes where there may not logically be one. For instance, a list of integers should not have any nodes that are used to keep a list of IP instances. If we declare a pointer to list_node for something, the compiler can't protect us from using pointers to the wrong list_node subclass.
150
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Templates solve all of these problems by letting you declare the class as a template, with placeholders for the missing parts that are provided at the location the variables are declared. Common functionality is declared exactly once - in the template definition, so it only must be modified in one place when changes are necessary. The classes that get generated do not have an inheritance relationship between them, so you can't blindly mix them together. Classes are dynamically generated at the point of usage, so unused classes don't clutter up object code and library files.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
151
template<class C> class list_node { list_node<C> *next, *prev; public: C data; list_node<C> *get_next() { return next; } list_node<C> *get_prev() { return prev; } void insert_before(list_node<C> *node) { node->prev=prev; node->next=this; prev->next=node; prev=node; } }
152
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Now, to make a list of integers (or floats, or objects), you just declare your nodes using the template. For example
These are dynamically-generated types. All are of the form of the list_node template, but with the type in the brackets substituted for C in the template. These types are separate classes that do not have an inheritance relationship among them.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
153
Template Parameters
There may be multiple template parameters. For instance, a template to declare an unknown-size array of unknown types might be declared as:
template<class C, int i> struct myarray { C data[i]; int size; myarray() { size = i; } }
154
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Template Instantiation
struct __some_private_name1__ { int data[10]; int size; __some_private_name1__() { size = 10; } } struct __some_private_name2__ { foo data[1000]; int size; __some_private_name2__() { size = 1000; } }
The private names used are implementation-defined, but are consistent, in order to allow type-compatibility.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
155
Template parameters can have default values just like function parameters:
template<class C, int i=10> struct myarray { C data[i]; int size; myarray() { size = i; }
a; b; c; d;
156
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
min() Example
Templates may be used to dynamically declare functions as well as types and classes. For example, the standard "minimum of two objects" may be implemented as a template function:
157
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
min() Example
Usage of the template version of min() is just like the macro version:
int a, b; foo c, d; // Assume these are initialized if (min(a,b)) ...; if (min(c,d)) ...;
Note that the type T is induced from the arguments, so there's no need to specify it. Each usage with different types ends up creating a new function. Memory is not wasted in this case because the template function is declared inline.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
158
Template Inheritance
Template classes may inherit from other classes (even other template classes). For example:
template<class T> class List<T*> : public list_node { ... }; template<class T> class vector { ... }; template<class T> class Vec1 : public vector<void*> { ... }; template<class T> class Vec2 : public vector<T> { ... };
159
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Type Induction
I pointed out that the type T is induced from the arguments. It is sufficient to write:
160
Type Induction
The compiler is able to induce the template parameter value from the function arguments. Since the template parameter is used as the type of the function parameters, it can simply use those parameter types as the template parameter's type. Of course, if the two parameters were different:
Then the type of the template parameter would be ambiguous and we would have to specify it.
161
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Type Induction
As an interesting curiosity, type induction can be used to implement a version of sizeof that returns the number of elements in an array (instead of the number of bytes):
template <class T, int size> inline int arraySize( T(&)[size]) { return size; } int a[12]; foo j[37]; arraySize(a); // returns 12 arraySize(j); // returns 37;
162
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
More Information
Templates are a complicated part of C++. Entire books have been written describing all of the things that they are capable of. A lot of the standard C++ library is based on templates. See
http://dcserver/~dcharlap/sd99/advanced_cxx_templates.html
for my lecture notes from the Software Development 1999 conference, regarding advanced uses of templates.
163
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction To C++
Namespaces
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
When designing libraries, you declare a lot of public symbols. This includes functions, classes, enums, constants, etc. In order to prevent these symbols from conflicting with symbols in user code (or other modules of your library), it is typical to prefix symbols with library- or module-names. For example
165
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
This can quickly become difficult to maintain. Usage can also become annoying. Especially when your library name and module name are relatively long.
166
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Namespace Example
Namespaces solve this. You declare a namespace and put your symbols in it. The symbols can be short and descriptive this way. For example:
namespace ospf { struct INTF *mgr_create_intf(...); } namespace rsvpte { typedef ... interface_t; interface_t *mgr_create_intf(...); } namespace ldp { typedef ... err_t; err_t mgr_create_intf(...); }
167
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Namespace Example
Of course, this doesn't look any better than simply sticking prefixes on the names. So additional features were defined to manage namespaces.
168
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Namespaces
A namespace is a scope. The usual scope rules apply to namespaces. This means that if a name is previously declared in the namespace or an enclosing scope, you don't need to explicitly name the namespace. For instance:
...
return i; }
169
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
using Declarations
This works fine for simple cases, where a function sticks with symbols from its own namespace. But what if you frequently need symbols from another namespace? You can, of course, fully qualify the symbols but that can quickly become tedious and hard to read. Instead, you can use a using-declaration to alias a single symbol into the current scope:
170
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
using Directives
You can also alias an entire namespace into the current scope using a using-directive:
171
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
using Directives
A using-directive at the global level effectively deletes the namespace. This should only be used as a transitional tool, while porting code over to a namespace. A using-directive inside a namespace definition can be used to create namespaces that are composites of other namespaces. A using-directive inside a function can be safely used as a notational convenience, since it won't have global impact on how code compiles.
172
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Anonymous Namespaces
You can also declare an anonymous namespace. Without naming the namespace, its symbols can only be accessed from functions declared and defined within that namespace. For example:
173
Anonymous Namespaces
In order to make anonymous namespaces useful, every one has an implied using-directive at the end of it. In the previous example, f and g can be called from any code in the same translation unit as the namespace's definition. If the same anonymous namespace appears in a different translation unit, it will be a different namespace. In other words, anonymous namespaces can make functions private in the same fashion that the static keyword does in C. It should be obvious that anonymous namespaces in headers are pointless and will lead to much confusion.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
174
Namespaces
A function taking an argument of type T is usually defined in the same namespace as T. Because of this, if a function isn't found in the context where it is used, the compiler will look in the namespaces of its arguments. This saves a lot of typing, compared to explicit qualification, but doesn't pollute the namespace the way a using-directive can. Note that the namespace and the function must still be declared and in-scope for this to work.
175
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Namespace Aliases
In order to avoid conflicts, using long, descriptive namespace names is a good idea. But long names are a pain to type in all the time. To resolve this problem, namespace aliases can be declared. For instance:
176
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Derived Namespaces
You can derive namespaces from other namespaces. For instance, we might declare each sub-module of RSVP in a separate namespace and then compose an overall RSVP namespace from the set of them:
namespace rsvpte_mgr { ... } namespace rsvpte_parser { ... } namespace rsvpte_state_machine { ... } namespace { using using using } rsvpte namespace rsvpte_mgr; namespace rsvpte_parser; namespace rsvpte_state_machine;
177
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Namespace Selection
If you start combining namespaces with using-directives, you can wind up with name conflicts. You can, of course resolve these by specifying the namespace when the conflicting names are used, but this is ugly. Instead, you can use a using-declaration in the combined namespace. This will resolve the conflict by letting you specify which namespace to take the conflicting symbol from. This is known as namespace selection
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
178
For an example of selection, imagine a private String class (String is a standard library class) defined in a namespace. This namespace also contains two overrides for the plus operator, for concatenation:
namespace Library_string { class String { ... }; String operator+(const const String operator+(const const }
179
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
This can get really messy. If I include the standard library's String class header at the same time, it will be ambiguous which class I end up using when declaring variables of type String. I can, however, declare my own namespace that selects the symbols I need from Library_string, and use that:
Note that this pulls all the overloads of operator+ into the namespace. They are all the same symbol.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
180
Using selection like this, any new methods on Library_string::String will be automatically pulled into the My_String namespace. Ditto for any new overloads of Library_string::operator+. Similarly, code using the My_String namespace will be alerted (through compiler errors) if one of the Library_string symbols should get deleted. If we didn't do this, but instead used a using-directive to pull Library_string's symbols into scope, deletion of those symbols would result in code silently using the public String class instead. Which is probably not what we want.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
181
Finally, it should be noted that namespaces are open. You can add symbols to a single namespace from many locations. For instance:
At this point, the namespace A contains both f and g. This allows us to distribute a namespace's definition across multiple header files, to make code easier to understand.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
182
When defining a function that's declared in a namespace, you should explicitly state the namespace to prevent errors. For example, with this namespace:
183
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Then you would have accidentally defined a new symbol Mine::ff. The compiler won't have any way of knowing that the function name is actually a typo.
184
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
More Information
See
http://dcserver/~dcharlap/sd99/namespaces_and_their_impact_ on_cxx.html for my lecture notes from the Software
185
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction To C++
Multiple Inheritance
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Multiple Inheritance
When defining a hierarchy of classes, sometimes a subclass logically should inherit from two or more base classes. C++ allows this multiple inheritance using standard language features.
187
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Satellite Example
For example, consider a simulation environment where activity is represented by a class Task, and I/O is performed by a class Displayed. We can then define a class to represent a simulated object that both performs activity and I/O:
188
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Satellite Example
The subclass inherits all data members and methods from both superclasses, in addition to anything it defines locally. For example:
189
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Satellite Example
Similarly, because Satellite inherits from both Task and Displayed, it can be passed to functions that expect either class:
void hilight(Displayed *); void suspend(Task *); void g(Satellite *p) { hilight(p); // gets a pointer to the Displayed part suspend(p); // gets a pointer to the Task part }
190
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Satellite Example
Virtual functions work as usual. A function calling a virtual method of a Task or Display pointer will still call the override if passed a pointer to the Task or Display part of a Satellite.
191
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Resolving Ambiguity
There is room for ambiguity, however. Two base classes may have member functions with the same name. For example:
class Task { ... virtual debug_info *get_debug(); }; class Displayed { ... virtual debug_info *get_debug(); };
192
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Resolving Ambiguity
When the ambiguous method is called, the specific superclass used must be resolved:
193
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Resolving Ambiguity
Explicit resolution of the ambiguity is ugly, however. It is often better to merge the functionality using an override of the ambiguous function. For instance:
debug_info *Satellite::get_debug() { debug_info *dip1, *dip2, *dip3; dip1 = dip2 = dip3 = return } Task::get_debug(); Displayed::get_debug(); dip1->merge(dip2); dip3;
194
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Resolving Ambiguity
By localizing the ambiguous resolution into a single function, it means subclasses of the multiply-inherited class don't have to worry about the ambiguity. Subclasses can use whatever qualification they need to resolve calls. For instance:
class Telstar : public Satellite { ... void draw () { draw(); // recursive Satellite::draw(); // finds Displayed::Draw Displayed::draw(); Satellite::Displayed::draw(); // redundant } };
195
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction To C++
Virtual Base Classes
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Sometimes, your class may inherit from two classes that in turn inherit from a common base class. For example, we might define a class (Storable) for any object that can write/read itself to/from a file:
class Storable { public: virtual const char *get_file() = 0; virtual void read() = 0; virtual void write() = 0; virtual ~Storable() { write(); } };
197
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
It is logical that all classes that want to be able to store themselves to disk would inherit from Storable:
class Transmitter : public Storable { public: virtual void write(); ... }; class Receiver : public Storable { public: virtual void write(); ... };
198
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Now, let's define a Radio class that consists of both a Transmitter and a Receiver:
class Radio : public Transmitter, public Receiver { public: virtual const char *get_file(); virtual void read(); virtual void write(); ... };
Note that Radio has two Storable superclasses! One inherited from Transmitter and the other from Receiver.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
199
Typically, an implementation would call the superclass methods and then call its own private implementation:
This happens to work OK because the Storable class can be safely replicated. It contains no data and its write() method is pure virtual.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
200
class Storable { const char *store; Storable (const Storable&); Storable& operator= (const Storable&); public: Storable(const char *s); ... };
This is a problem! There are now two instances of the data in a Radio. Which one gets used? This ambiguity will lead to unpredictable behavior.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
201
What we need to do is to make sure that there is only one Storable component in a Radio. We do this by making the immediate subclasses of Storable virtually inherit from it:
class Transmitter : public virtual Storable { ... }; class Receiver : public virtual Storable { ... };
202
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
All classes that virtually inherit from the same base class will share the same instance of that base class if they are later combined using multiple inheritance. A class that does not virtually inherit from its base class will always get a unique copy of that base class if it is combined with a sibling class using multiple inheritance.
203
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Note that in implementations, you may have issues with the base class's methods being called multiple times for a single instance of a subclass. This is often incorrect behavior. Especially if the calls have side effects. For example, some classes for windows:
Window - a plain window Window_with_border - a window with a border Window_with_menu - a window with a menu bar
204
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
205
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
class Window { public: virtual void draw(); // Draw the window }; class Window_with_border : public virtual Window { public: virtual void draw(); }; class Window_with_menu : public virtual Window { public: virtual void draw(); };
void Window_with_border::draw() { Window::draw(); // draw a border } void Window_with_menu::draw() { Window::draw(); // draw a menu bar }
206
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Now let's say that we are defining an application window that has both a border and a menu bar:
class Clock : public Window_with_border, public Window_with_menu { public: virtual void draw(); };
207
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Notice that Window::draw is called twice! This will waste CPU time and may result in more serious problems, depending on its side effects.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
208
The C++ language doesn't provide a solution for this, but the problem can be avoided through some clever organization of the classes. To do this, we declare separate non-virtual draw_self methods in all of the subclasses and have the draw methods call these. Then in the application class, we can explicitly call the base-class's draw method followed by the non-virtual draw_self methods that we need to call without worrying about the calls propagating to the base class.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
209
class Window_with_border : public virtual Window { public: virtual void draw(); void draw_self(); }; class Window_with_menu : public virtual Window { public: virtual void draw(); void draw_self(); };
210
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
211
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Now our application's draw method can be written in a way that doesn't call Window::draw() more than once:
This can still cause some maintenance problems, but it solves the run-time problem.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
212
Multiple Inheritance
There is a lot more to multiple inheritance than just this. For more information, see section 15 of The C++ Programming Language
213
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction To C++
Run-Time Type Identification (RTTI)
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Subclasses are only assignment compatible in one direction. You can assign a subclass pointer/reference to a base class pointer/reference, but not the other way around:
class a; class b : public a; a aObj; b bObj; a* aPtr = &bObj; // Perfectly legal b* bPtr1 = aPtr; // Illegal b* bPtr2 = &aObj; // Illegal
215
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
One solution is to simply apply a cast, but with some serious problems if it's not applied correctly:
class a; class b : public a; a aObj; b bObj; a* aPtr = &bObj; b* bPtr1 = (b*)aPtr; // This works b* bPtr2 = (b*)&aObj; // This shouldn't have worked!
Casting also won't recompute pointer values, which is necessary when doing this with multiple inheritance.
216
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Dynamic Cast
The solution is the dynamic_cast operator. It examines the actual type of the data and will do the right thing:
class a; class b : public a; a aObj; b bObj; a* aPtr = &bObj; b* bPtr1 = dynamic_cast<b*>(aPtr); b* bPtr2 = dynamic_cast<b*>(&aObj);
After executing this, bPtr1 will have the correct pointer value. bPtr2 will have NULL, since object aObj is incompatible with object class b.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
217
Dynamic Cast
dynamic_cast will return NULL whenever the object and class are not compatible with each other. When there are multiple ambiguous base classes (such as when using some extreme cases of multiple inheritance) it will also return NULL. The base class in a dynamic_cast must be polymorphic. That is, it must have at least one virtual method. Objects of non-polymorphic classes don't necessarily have the internal pointers to RTTI data that dynamic_cast requires in order to work.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
218
Dynamic Cast
dynamic_cast works on references as well as pointers. But because there is no such thing as a NULL reference, dynamic_cast will throw a bad_cast exception when the cast can not be performed.
class a; class b : public a; a aObj; b bObj; a& aRef = bObj; b& bRef1 = dynamic_cast<b&>(aRef); b& bRef2 = dynamic_cast<b&>(aObj);
219
Dynamic Cast
When using dynamic_cast with references, it is a very good idea to use an exception handler.
class a; class b : public a; a aObj; try { b& bRef = dynamic_cast<b&>(aObj); } catch(bad_cast) { // The cast failed. Do something about it. }
220
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
static_cast does not check an object's RTTI data to see if the cast is possible, but it does do the pointer arithmetic needed for casting from one of several base classes in a multiple inheritance hierarchy (unlike a C-style cast). static_cast can not cast from a virtual base class in multiple inheritance, however, since this is impossible without referencing the RTTI class data that dynamic_cast uses (and static_cast does not use.)
221
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
The pointer/reference may be to data that isn't polymorphic (like, from legacy C code) The data may be a (void *) - which by definition can not be assumed to point to anything specific and therefore has no accessible RTTI information. dynamic_cast has some overhead, although it's not much.
222
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
dynamic_cast is usually useful enough for the situations where RTTI is required. But sometimes you need more specific information about a class. For that, there is the typeid operator. Like the sizeof operator, typeid may be applied to a type, an object, a pointer, a reference, or any other expression. typeid returns a reference to a type_info structure (defined in the <typeinfo> header) that identifies the type.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
223
#include <typeinfo> void f(Shape &r, Shape *p) { type_info &ts, &tr, &tpp, &tp;
ts = typeid(Shape); // Info about Shape tr = typeid(r); // Type of the reference tpp = typeid(*p); // Type of the data pointed to tp = typeid(p); // Type of the pointer itself }
224
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
class type_info { public: virtual ~type_info(); bool operator==(const type_info&) const; bool operator!=(const type_info&) const; bool before(const type_info&) const; const char *name() const; private: type_info(const type_info&); type_info& operator=(const type_info&); };
225
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
The destructor (~type_info) is to ensure that the class is polymorphic. The == and != operators are used to test equality. It is important to use these on the type_info objects and not simply comparing the values of pointers to type_info objects, because there may be several type_info objects corresponding to a single type. The before() method is used for ordering the types (for sorting.) The order is arbitrary - it only has to be consistent within a single run of the program. The name() method retrieves the type's name The private copy constructors prevent accidental duplication of type_info objects.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
226
Most of the time, there will be no need for any RTTI (dynamic_cast or typeid). Virtual methods, inheritance, and templates provide most of the application-level features that many programmers try to gain through use of RTTI. There are, however, some legitimate situations where RTTI may be needed, so don't design an overly complicated solution simply to avoid using it.
227
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
More Information
See http://dcserver/~dcharlap/sd99/rtti.html for my lecture notes from the Software Development 1999 conference, regarding run-time type indentification.
228
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction to C++
Part 4: The Standard C++ Library
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
All of the features of the C library are available in C++. You can simply include the same headers as before:
230
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
A preferable way, however, is to use the new headers for these components. The header names are the same as before, but with a c prefix and without the .h suffix. The symbols declared in the new headers are all in the std namespace, in order to avoid collision with user code. For example:
231
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
New Headers
Most of the new headers do not have any suffix on their filenames. For instance:
All standard library symbols are in the std namespace, although some pre-standard compilers may not yet have put them where they belong.
232
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
I/O Streams
#include <iostream> I/O streams are a mechanism for objects to write their data out to text and binary streams, and to read their data back from those streams. In the simplest form, they may be used in place of the standard C library's printf/scanf functions. The standard streams cin, cout, and cerr (actually std::cin, std::cout and std::cerr) allow access to the standard I/O handles stdin, stdout, and stderr, respectively.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
233
I/O Streams
is equivalent to:
int x, y, z; std::cout << "x is " << x << " y is " << y << " z is " << z << '\n';
(I won't be pedantically mentioning the std:: namespace in the rest of my examples. Assume they're there.)
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
234
I/O Streams
is equivalent to
int x, y, z; char buffer[10]; cin >> x; cin >> y; cin >> z; cin.width(10); cin >> buffer;
235
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
I/O Streams
More complicated data can be read and written by overloading the << and >> operators. For example, one possible way to import and export JPEG image files might be:
class jpeg { ... }; ostream& operator<<(ostream &s, const jpeg &j); istream& operator>>(istream &s, jpeg &j); jpeg j; cin >> j; cout << j;
236
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
I/O Streams
Formatting of stream input and output can be performed by calling various methods on stream objects. Streams that read and write files can be created using the ifstream and ofstream classes (in the <fstream> header.) Streams that read and write strings can be created using the istringstream and ostringstream classes (in the <sstream> header.)
237
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction to C++
Containers
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Containers
Containers are object classes that are used to store other objects. There are different kinds of containers that store the objects in different ways, optimized for different access patterns:
Sequences (vector, list, deque) Sequence adaptors (stack, queue, priority queue) Associative types (map, multimap, set, multiset) "Almost" containers (string, valarray, bitset, array)
239
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Containers
All containers are template classes. When instantiating a container, you specify the type of object you want it to contain. For example:
240
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Iterators
All sequence-containers use iterators for traversal. There are several kinds of iterators:
Output (Out) Input (In) Forward (For) Bi-directional (Bi) Random-Access (Ran)
241
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Iterators
242
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Iterators
Iterator operations are performed via overloaded operators. Note that not all operators are applicable to all kinds of iterators:
Input
Output
Forward = *p ->
= *p -> *p = ++ == != ++
*p = ++ == !=
243
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Iterators
There is a lot more to say about iterators, but we won't discuss them here, since most of their use is in the context of the objects they iterate over (e.g. containers, strings, etc.) See chapter 19 of The C++ Programming Language for all the details.
244
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Vector
A vector is the most basic kind of container. Other standard containers support most of vector's functionality. Some containers add additional functionality. Unless otherwise noted, all containers share vector's interface.
245
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Vector
Defined in the <vector> header A vector is effectively a dynamically-resizing array. It has the following features:
Iterators for traversing the list. The subscript operator may be used for random access Stack operations (push, pop, etc.) List operations (insert, delete, etc.)
246
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Vector Types
value_type - the type of each element allocator_type - the type of the memory manager object size_type - the type used for indexing elements difference_type - the type resulting from subtracting two iterators iterator, const_iterator - for forward traversal reverse_iterator, const_reverse_iterator - for backward traversal pointer, const_pointer - pointer to an element reference, const_reference - reference to an element
247
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Vector Types
These types allow member function to access the container without knowing anything about the actual objects. For example, a template function to add up all the elements in a container:
template<class C> typename C::value_type sum(const C& c) { typename C::value_type s = 0; typename C::const_iterator p = c.begin(); while (p != c.end()) { s += *p; // dereference an iterator to get the object ++p; // increment an iterator to go to the next object } return s; }
248
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Vector Iterators
iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; reverse_iterator rbegin(); const_reverse_iterator rbegin() const; reverse_iterator rend(); const_reverse_iterator rend() const;
249
The begin() functions point to the first element in the (forward or reverse) sequence. The end() functions point to one element beyond the last element in the (forward or reverse) sequence. Vector's iterators are random-access
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Vector Iterators
Note that a reverse_iterator is a distinct type from an iterator. They are not interchangeable. The base() method of a reverse iterator returns an iterator that points to a position one beyond the position pointed to by the reverse_iterator. For example, to find the last instance of a value, you might use:
template<class C> typename C::iterator find_last( C& c, typename C::value_type v) { typename C::reverse_iterator ri = find(c.rbegin, c.rend(), v); if (ri == c.rend()) return c.end(); typename C::iterator i = ri.base; return --i; }
250
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Unchecked access:
reference operator[] (size_type n); const_reference operator[] (size_type n) const; reference at(size_type n); const_reference at(size_type n) const; reference front(); const_reference front() const; reference back(); const_reference back() const;
First element:
Last element:
251
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
template <class T, class A=allocator<T> > class vector { public: // Construct an empty vector explicit vector(const A& = A()); // Construct vector with n copies of val explicit vector(size_type n, const T& val = T(), const A& = A()); // Construct a range of objects (In is an input iterator type) template<class In> vector(In first, In last, const A& = A()); // Construct from another vector vector(const vector & x);
252
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
// Destructor ~vector(); // Copy from a range of objects (In is an input iterator) template<class In> void assign(In first, In last); // Copy from n copies of val void assign(size_type n, const T& val); };
253
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
void pop_back();
Watch out for underflow - don't pop an empty vector. This results in undefined behavior and may corrupt memory:
vector<int> v; v.pop_back(); // Undefined behavior, v's state is now undefined v.push_back(7); // Undefined behavior - v's state was undefined
254
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
255
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
void clear();
256
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
List operations (among others) require iterators. How do you get an iterator from an index? For example, how do you delete a specific element from a vector?
c.erase(c.begin() + 7); // OK, if c's iterator supports + c.erase(&c[7]); // Bad. assumes too much about vector's // implementation c.erase(c+7); // Bad. adding 7 to a container makes no sense c.erase(c.back()); // Bad. back() returns a reference, not // an iterator c.erase(c.rbegin()+2); // Bad. iterator and reverse-iterator // are different types c.erase(c.end()-2); // OK (deletes second-to-last element) c.erase((c.rbegin()+2).base()); // OK, but obscure
257
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
size_type size() const; bool empty() const; size_type max_size() const; void resize(size_type sz, T val=T()); size_type capacity() const;
Make room for n elements (pre-allocation), throw a length_error exception if n > max_size()
258
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Swap two vectors (more efficient than actually copying them to do a swap):
The == operator is overloaded for comparing two vectors. They're equal if they have the same number of elements and matching pairs of elements all compare equal using their == operators. The < operator is overloaded for comparing two vectors, using lexicographic ordering. Similarly, there are overloads for !=, <=, > and >= These allow you to make a container of vectors
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
259
vector<bool>
There's a specialized version of the vector template, used for keeping a vector of Boolean values. All vector operations work normally on vector<bool>. Note that iterators for vector<bool> can not be implemented as pointers to the objects, since you can't take the address of a single bit. Your code should never assume any particular representation for any kind of iterator object.
260
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Do not assume anything about the internal representation of a container. The standard doesn't define any specific representation. If you assume any specifics, your code will break when ported to other platforms/compilers.
261
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Elements in a container are copies of the objects inserted. Elements therefore require a compatible assignment operator. For example:
X& X::operator=(const X& a) { // copy all of a's members to this return *this; }
If it would be bad to make copies of objects, you can make a container of pointers instead. For example:
Instead of:
262
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Another reason for making a container of pointers instead of objects is in order to preserve polymorphic behavior. You can't store a derived object in a container of base objects, because the copy constructor won't copy the derived object's fields. When you retrieve the object, the original (derived) class data will be lost. You can, however, store a pointer to a derived object in a container of pointers to the base class. When you retrieve the object, virtual methods will continue to work as expected, and RTTI may be used to determine the specific subclass (if this is necessary).
263
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Associative containers require that their elements can be sorted. Some operations (like sort()) also require that elements can be sorted. By default, the < operator is used to define order. If this is not suitable, then an alternative mechanism (involving a user-provided compare function) may be used, as long as the compare function is transitive:
cmp(x,x) must be false If cmp(x,y) and cmp(y,z) then cmp(x,z) Defining equiv(x,y) to be !(cmp(x,y) || cmp(y,x)), If equiv(x,y) and equiv(y,z) then equiv(x,z)
264
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Note that the < operator, when used on C-strings (char pointers) will compare pointer values, not strings. This may lead to unexpected behavior if a container of char pointers is made. A compare function or overload for the < operator that calls strcmp may be used to make this work. For example:
struct Cstring_less { bool operator< (const char *p, const char *q) const { return strcmp(p,q) < 0; } }; map<char *, int, Cstring_less> m;
265
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
List
Defined in the <list> header A list is a sequence optimized for insertion and deletion. Its methods are similar to vector. There are no subscripting operators. The capacity() and reserve() methods do not exist. List iterators are bi-directional List may be thought of like a doubly-linked list, but you should not make any assumption about the implementation, because it may be something else.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
266
List Splicing
List splicing allows you to move elements from one list to another without copying the objects. The following methods are provided for this:
Move all elements from x to before pos in this list without copying
Move a range of objects from x to before pos in this list without copying
267
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
In addition to splicing, lists may be sorted, and sorted lists may be merged together with the following methods:
Sort a list
268
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
All containers have the back() method to reference the last element, and the push_back()/pop_back() methods for stack operation. Lists provide similar methods for the front of the list:
void pop_front();
269
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
void unique();
void reverse();
270
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Deque
Defined in the <deque> header A deque is a double-ended queue. Push/pop operations at either end (front or back) are efficient (like a list) Subscript operations are efficient (like a vector) Insertions/deletions are inefficient (like a vector) Iterators are random-access
271
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Sequence Adaptors
An adaptor is not a class, but is an interface to another class. There are three kinds of adaptors for the sequence templates:
272
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Stack
Defined in the <stack> header An interface to its corresponding container (deque by default). Non-stack operations are eliminated. Only stack operations (push to one end, pop from the same end) exist. back(), push_back(), and pop_back() are renamed with more conventional names: top(), push() and pop()
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
273
Stack
Any container that implements back(), push_back() and pop_back() may be used in a stack. Examples:
stack<char> s1; // made from deque stack<int, vector<int> > s2; // made from vector
You can initialize a stack with another container, but it may be expensive, since elements are copied:
274
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Queue
Defined in the <queue> header An interface to its corresponding container (deque by default) Non-queue operations are eliminated. Only queue operations (push to one end, pop from the other end) exist. push_back() and pop_front() are renamed with more conventional names: push() and pop().
275
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Queue
Any container that implements front(), back(), push_back() and pop_front() can be used in a queue. Note that this excludes vector
276
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Priority Queue
Defined in the <queue> header Like a queue, but elements are kept sorted. Unlike queue, top() retrieves the next-to-be-popped element, instead of front() By default, elements are compared with the < operator, and top() returns the largest element A comparison function may be provided if the < operator is not appropriate
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
277
Associative Containers
Associative containers store objects with key values. The different kinds of associative containers are:
map - a sequence of key/value pairs, sorted by key. keys are unique multimap - keys not unique set - a sequence of keys only. keys are unique multiset - keys are not unique
Most associative container operations are implemented by map (much like most sequence operations are implemented by vector)
278
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Map
Defined in the <map> header An associative container. Sometimes referred to as a dictionary. Similar in concept to our BBT trees. A map is a sequence of key/value pairs. Each key in a map is unique. Iterators are bi-directional. Elements are kept sorted by their keys
279
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Map Types
key_type - the type of a key mapped_type - the type of the data value_type - the type of each key/value pair key_compare - the comparison function allocator_type size_type difference_type reference, const_reference iterator, const_iterator reverse_iterator, const_reverse_iterator
280
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Map Iterators
Map's iterators work like sequence iterators, but the values they refer to are key/value pairs. Dereferencing a map iterator will return an object of the template class pair<key_type, mapped_type>. This object class is also the value_type definition In each pair, the first element is the key and the second element is the value.
281
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Pairs
The two elements are named first and second The public part of the class definition is:
template<class T1, class T2> struct std::pair { typedef T1 first_type; typedef T2 second_type; T1 first; T2 second; pair() : first(T1()), second(T2()) {} pair(const T1& x, const T2 &y) : first(x), second(y) {} template<class U, class V> pair(const pair<U,V>& p) : first(p.first), second(p.second) {} };
282
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
If the key is not found, an element is added to the map with default values For example:
map<string, int> m; int x = m["Henry"]; m["Harry"] = 7; int y = m["Harry"]; m["Henry"] = 9; m["Harry"] = 37;
283
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Map Constructors
// Construct an empty map explicit map(const Cmp& = Cmp(), const A& = A()); // Construct from a list of key/value pairs (In is an input iterator) template <class In> map(In first, In last, const Cmp& = Cmp(), const A& = A()); // Construct from another map map(const map&); // Destructor ~map(); // Assignment operator overload map &operator= (const map&);
284
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
By default, keys are compared using the < operator. A map can be created using other compare functions. For example:
Default comparison class (less<string>) map<string, int> m1; Use Nocase (to be constructed by map) instead map<string, int, Nocase> m2; Use String_cmp (to be constructed by map) instead map<string, int, String_cmp> m3; Use a String_cmp object that we constructed ourselves map<string, int, String_cmp> m4(String_cmp(literary));
285
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
The functions used for comparing keys and values (which are key/value pairs) are publicly available. They are called key_comp() and value_comp(), respectively. This allows you to pass one map's comparison function to another. For example:
void f(map<string, int>& m) { // mm compares keys with < map<string, int> mm; // mmm compares keys with whatever function m uses map<string, int> mmm(m.key_comp()); }
286
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Map Operations
Find the element (or first element, for multimap/multiset) with a given key:
287
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Map Operations
Find the first element with a key greater than or equal to a given key:
Find the first element with a key greater than a given key:
288
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Map Operations
Find both the lower bound and upper bound together in a pair (lower bound is first, upper bound is second):
pair<iterator, iterator> equal_range(const key_type& k); pair<const_iterator, const_iterator> equal_range(const key_type& k) const;
289
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
290
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Erase an element
Erase all elements with a given key (return value is the number actually erased):
void clear();
291
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
292
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
void swap(map&);
Two maps may be compared using the ==, !=, <, >, <=, and >= operators Although these are not terribly useful, they allow us to make containers that contain maps
293
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Multi-Map
A multimap can do everything a map can do. In addition, multiple elements may share the same key. There is no subscript operator in a multimap, since it is a meaningless concept. The insert() function returns an iterator instead of a pair:
294
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Multi-Map Example
multimap<char *, int, Cstring_less> mm; mm.insert(make_pair("x", 4)); mm.insert(make_pair("x", 5)); // mm now holds both ("x",4) and ("x",5);
295
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Set
Defined in the <set> header A Set is like a map, except that no values are stored. You can only access keys. value_type is the same as key_type value_compare is the same as key_compare There is no subscript operator
296
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Multi-Set
A multiset is a set that allows duplicate keys A multiset's interface is identical to set's, with one exception: The insert() function returns an iterator instead of a pair:
297
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Almost-Containers
There are four other kinds objects that are not containers, but share many similar properties to containers. These are:
298
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
String
A string provides subscripting, random-access iterators and most of the same interface as a container, but it does not provide for a wide selection of types as elements. Strings are optimized for use as a string of characters and has methods that allow them to be used in places where string operations are traditionally used Strings have an associated "character traits" object which defines the properties of the character type that the string stores. This allows for use with non-standard character encodings.
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
299
Basic String
template<class Ch, class Tr=char_traits<Ch>, class A=allocator<Ch> > class std::basic_string { ... }
300
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Most of basic_string's functionality is the same as vector's. The similarities will not be discussed here. Some of the differences are:
The iterators are not range-checked There is no front() or back(). The subscript operator and length method must be used instead. For example:
string foo; // (Assume this to be initialized) char first_char = foo[0]; char last_char = foo[foo.length()-1];
301
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
String Constructors
basic_string(const basic_string &s, size_type pos=0, size_type n=npos, const A& a = A()); basic_string(const Ch* p, const A& a = A()); basic_string(const Ch* p, size_type n, const A& a = A()); basic_string(size_type n, Ch c, const A& a = A());
302
template<class In> basic_string(In first, In last, 2004. The Copyright in this document belongs to Marconi and no part of const A& a = A()); this document should be used or copied without their prior written permission.
String Constructors
String destructor:
~basic_string();
303
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
s0; // Empty String s00 = ""; // Empty string s1 = 'a'; // Error - no conversion from char s2 = 7; // Error - no conversion from int s3(7); // Error - no one-argument constructor s4(7, 'a'); // Initialized to "aaaaaaa" s5 = "Foobie"; // OK s6 = s5; // OK s7(s5,2,3); // Initialized to "obi"
304
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
char *p = ...; // Some external C string string s8(p+7, 3); // Initialized to p[7], p[8] and p[9] string s9(p, 7, 3); // Same, but more expensively vector<char> v = ...; string s10(v.begin(), v.end());
Note that this last example is very generic. The iteratorbased constructor will work on any iterator that dereferences to a type that can be converted to char.
305
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
String Manipulations
There are a very large number of methods for manipulating strings. They are far too numerous to list here, given the huge number of overloads that exist. The main categories of string manipulation methods are:
306
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Note that the string owns the returned values from data() and c_str(). Do not free the memory. Do not cache the pointers. The memory may become invalid after the next non-const method call on the string. Copy the characters to an array (does not append null!):
307
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
String I/O
The << method is overloaded to write a string to an output stream The >> method is overloaded to read a string from an input stream The getline method reads a line from an input stream (using a user-specified EOL character):
template<class Ch, class Tr, Class A> basic_istream<Ch,Tr>& getline(basic_istream<Ch,Tr>&, basic_string<Ch,Tr,A>&, Ch eol);
308
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
ValArray
A ValArray is a vector that is optimized for use in high speed numerical computing. The only standard container operations that are applicable to valarray are size (get the number of elements) and subscript (to read/write elements.) Because the goal of a valarray is to make vector math as fast as possible, other methods are implementation dependant, although there are typically many commonalities.
309
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
(similar overloads for /=, %=, += , -=, ^=, &=, |=, <<= and >>=) Sum up all elements:
T sum() const; valarray shift(int i) const; valarray cshift(int i) const; valarray apply(T f(T)) const; valarray apply(T f(const T&)) const;
310
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
There are many more things you can do with a ValArray, but we won't go into them because they are typically only useful for vector arithmetic (such as signal processing or games.)
311
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Bit-Set
Defined in the <bitset> header Designed to replace bit-flag manipulation. bitset<N> is a fixed-size array of N bits A bitset differs from vector<bool> due to its fixed size A bitset differs from a set because it is indexed by integer instead of associative keys
312
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Bit-Set
Because you can't have a pointer to a bit, bitset defines a "reference to bit" class to be used where references are necessary:
template<size_t N> class std::bitset { public: class reference { friend class bitset; reference(); public: ~reference(); reference& operator=(bool x); reference& operator=(const reference&); bool operator~(); operator bool() const; reference& flip(); }; };
313
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Bit-Set
Bitsets differ from other container classes (mostly for historical reasons).
Bit positions are numbered right-to-left. In other words the value of b[i] is pow(i,2). A bitset may be thought of as an N-bit binary number
314
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Bit-Set Constructors
bitset();
template<class Ch, class Tr, class A> explicit bitset(const basic_string<Ch,Tr,A>& str, basic_string<Ch,Tr,A>::size_type pos = 0, basic_string<Ch,Tr,A>::size_type n = basic_string<Ch,Tr,A>::npos);
315
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
b1; // All-zeros b2 = 0xaaaa; // 1010101010101010 b3 = 0xaaaa; // 00000000000000001010101010101010 b4("1010101010"); // 1010101010 b5("10110111011110", 4); // 0111011110 b6("10110111011110", 2, 8); // 0011011101 b7("n0g00d"); // invalid_argument exception b8 = "n0g00d"; // Err: no char* to bitset conversion
316
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Bit-Set Manipulation
The following operators are used for accessing and manipulating the bits in a bitset:
Bit-access (subscript)
reference operator[](size_t pos); bitset& operator&=(const bitset& s); bitset& operator|=(const bitset& s); bitset& operator^=(const bitset& s); bitset& operator<<=(size_t n); bitset& operator>>=(size_t n);
Logical operators
Logical shift
317
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Bit-Set Manipulation
Set bits to 1
bitset& set(); // Set all bits to 1 bitset& set(size_t pos, int val=1); // b[pos]=val bitset& reset(); // Set all bits to 0 bitset& reset(size_t pos); // b[pos]=0 bitset& flip(); // Toggle all bits bitset& flip(size_t pos); // Toggle b[pos] bitset operator~() const; bitset operator<<(size_t n); bitset operator>>(size_t n);
Set bits to 0
Toggle bits
318
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Bit-Set Operations
Make an unsigned long from the bitset (throws overflow_error exception if there are too many significant bits)
unsigned long to_ulong() const; template<class Ch, Class Tr, Class A> basic_string<Ch,Tr,A> to_string() const; size_t size() const; size_t count() const;
319
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Bit-Set Operations
Comparison
bool operator==(const bitset& s) const; bool operator!=(const bitset& s) const; bool test(size_t pos) const; bool any() const; bool none() const;
320
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Array
An array is the same in C++ as in C. There are subscripting operators for element access and random-access iterators (in the form of pointers). Arrays don't know their own size and do not have any of the usual member operations and types
321
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Arrays
It is possible, however to create a template that wraps an array to allow basic container functionality. For example:
int max> struct c_array { T value_type; T* iterator; T* const_iterator; T& reference; T& const_reference;
T v[max]; operator T*() { return v; } reference operator[](size_t i) { return v[i]; } iterator begin() { return v; } iterator end() { return v+max; } ptrdiff_t size() const { return max; } ...
322
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
};
323
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction to C++
Numerics
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Vector Arithmetic
Vector arithmetic is performed using the valarray class, which we've previously discussed Section 22.4 of The C++ Programming Language explains in great detail the things that can be done with ValArrays and related classes
325
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Complex
The standard C++ library also includes a complex arithmetic library. Defined in the <complex> header It is a template in order to allow complex numbers to be based on any scalar type (e.g. float, double, and long double)
326
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
template<class T> class std::complex { public: typedef T value_type; complex(const T& r = T(), const T& i = T()); template<class X> complex(const complex<X>&a);
T real() const; T imag() const; complex<T>& operator=(const T&z); template<class X> complex<T>& operator=(const complex<X>&); // Similarly: +=, -=, *=, /= // ... };
327
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Binary operators
Unary operators:
328
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
T real(const complex<T>&); T imag(const complex<T>&); complex<T> conj(const complex<T>&); complex<T> polar(const T& rho, const T& theta); T abs(const complex<T>&); // rho T arg(const complex<T>&); // theta T norm(const complex<T>&); // square of abs()
Generate conjugate:
329
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Trigonometric functions:
complex<T> sin(const complex<T>&); Similarly for sinh, sqrt, tan, tanh, cos, cosh, exp, log and log10
Exponentiation:
complex<T> pow(const complex<T>&, int); complex<T> pow(const complex<T>&, const T&); complex<T> pow(const complex<T>&, const complex<T>&); complex<T> pow(const T&, const complex<T>&);
330
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Overloads for the << and >> operators exist to allow complex numbers to be read/written from/to iostreams. Complex numbers are written in the form (x,y) Complex numbers may be read as either x, (x), or (x,y)
331
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Complex Specializations
This is done to restrict/facilitate type conversion and to provide opportunities for optimized libraries.
332
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Generalized Numerics
The <numeric> header provides a few generalized algorithms that perform common operations on arbitrary numeric types:
333
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Accumulate
The accumulate algorithm produces the sum of all elements its given (via two input iterators):
template<class In, class T> T accumulate(In first, In last, T init) { while(first != last) init = init + *first++; return init; }
334
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Accumulate
A more generalized version of accumulate also exists, that allows any binary operation to be performed on the set of elements:
template<class In, class T, class BinOp> T accumulate(In first, In last, T init, BinOp op) { while(first != last) init = op(init, *first++); return init; }
335
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Inner Product
An inner product is the result of summing the product of two sequences. That is, items at identical indices are multiplied together, and all of these products are summed up together:
template<class In, class In2, class T> T inner_product(In first, In last, In2 first2, T init) { while(first != last) init = init + (*first++ * *first2++); return init; }
336
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Inner Product
A more generalized version of inner_product also exists, that allows any two binary operations to be used in place of multiplication and addition:
template<class In, class In2, class T, class BinOp, class BinOp2> T inner_product(In first, In last, In2 first2, T init, BinOp op, BinOp2 op2) { while(first != last) init = op(init, op2(*first++, *first2++)); return init; }
337
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Adjacent Difference
Given a sequence of input values, generate a sequence that consists of the differences between adjacent values. Input sequence {a,b,c,d,...} produces {a,b-a,c-b,d-c,...} A basic function and one that uses a generalized operation (instead of subtraction) are provided for this:
template <class In, class Out> Out adjacent_difference(In first, In last, Out result); template <class In, class Out, class BinOp> Out adjacent_difference(In first, In last, Out result, BinOp op);
338
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Partial Sum
A partial sum allows us to compute the end result of a set of incremental changes. Input sequence {a,b,c,d,...} produces {a,a+b,a+b+c,a+b+c+d,...} A basic function and one that uses a generalized operation (instead of addition) are provided for this:
template <class In, class Out> Out partial_sum(In first, In last, Out result); template <class In, class Out, class BinOp> Out partial_sum(In first, In last, Out result, BinOp op);
339
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
Introduction to C++
Conclusion
BBRS IP Software Group
2003. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.
www.marconi.com
Conclusion
This presentation covers all of the basics There are more details not covered There is no substitute for experience What are you waiting for? Start writing some code!
341
2004. The Copyright in this document belongs to Marconi and no part of this document should be used or copied without their prior written permission.