Zach
Tip 1: <iostream.h> or <iostream>?
Many C++ programmers still use <iostream.h> instead of the newer,
standard compliant <iostream> library. What are the differences
between the two? First, the .h notation of standard header files was
deprecated more than five years ago. Using deprecated features in new
code is never a good idea. In terms of functionality, <iostream>
contains a set of templatized I/O classes which support both narrow
and wide characters, as opposed to <iostream.h> which only supports
char-oriented streams. Third, the C++ standard specification of
iostream's interface was changed in many subtle aspects.
Consequently, the interfaces and implementation of <iostream> differ
from those of <iostream.h>. Finally, <iostream> components are
declared in namespace std whereas <iostream.h> components are global.
Because of these substantial differences, you cannot mix the two
libraries in one program. As a rule, use <iostream> unless you're
dealing with legacy code that is only compatible with <iostream.h>.
Tip 2: Binding a Reference to an Rvalue
Rvalues and lvalues are a fundamental concept of C++ programming. In
essence, an rvalue is an expression that cannot appear on the
left-hand side of an assignment expression. By contrast, an lvalue
refers to an object (in its wider sense), or a chunk of memory, to
which you can write a value. References can be bound to both rvalues
and lvalues. However, due to the language's restrictions regarding
rvalues, you have to be aware of the restrictions on binding
references to rvalues, too.
Binding a reference to an rvalue is allowed as long as the reference
is bound to a const type. The rationale behind this rule is
straightforward: you can't change an rvalue, andonly a reference to
const ensures that the program doesn't modify an rvalue through its
reference. In the following example, the function f() takes a
reference to const int:
void f(const int & i);
int main()
{
f(2); /* OK */
}
The program passes the rvalue 2 as an argument to f(). At runtime, C++
creates a temporary object of type int with the value 2 and binds it
to the reference i. The temporary and its reference exist from the
moment f() is invoked until it returns; they are destroyed immediately
afterwards. Note that had we declared the reference i without the
const qualifier, the function f() could have modified its argument,
thereby causing undefined behavior. For this reason, you may only bind
references to const objects.
The same rule applies to user-defined objects. You may bind a
reference to a temporary object only if it's const:
struct A{};
void f(const A& a);
int main()
{
f(A()); /* OK, binding a temporary A to a const
reference*/
}
Tip 3: Comma-Separated Expressions
Comma-separated expressions were inherited from C. It's likely that
you use such expressions in for- and while-loops rather often. Yet,
the language rules in this regard are far from being intuitive. First,
let's see what a comma separated expression is.
An expression may consist of one or more sub-expressions separated by
commas. For example:
if(++x, --y, cin.good()) /*three expressions*/
The if condition contains three expressions separated by commas. C++
ensures that each of the expressions is evaluated and its side effects
take place. However, the value of an entire comma-separated expression
is only the result of the rightmost expression. Therefore, the if
condition above evaluates as true only if cin.good() returns true.
Here's another example of a comma expression:
int j=10;
int i=0;
while( ++i, --j)
{
/*..repeat as long as j is not 0*/
}
Tip 4: Calling a Function Before Program's Startup
Certain applications need to invoke startup functions that run before
the main program starts. For example, polling, billing, and logger
functions must be invoked before the actual program begins. The
easiest way to achieve this is by calling these functions from
a constructor of a global object. Because global objects are
conceptually constructed before the program's outset, these functions
will run before main() starts. For example:
class Logger
{
public:
Logger()
{
activate_log();
}
};
Logger log; /*global instance*/
int main()
{
record * prec=read_log();
//.. application code
}
The global object log is constructed before main() starts. During its
construction, log invokes the function activate_log(). Thus, when
main() starts, it can read data from the log file.
Tip 5: Hiding the Cumbersome Syntax of Pointers to Functions
Can you tell what the following declaration means?
void (*p[10]) (void (*)());
p is an "array of 10 pointers to a function returning void and taking
a pointer to another function that returns void and takes no
arguments." The cumbersome syntax is nearly indecipherable, isn't it?
You can simplify this declaration considerably by using typedefs.
First, declare a typedef for "pointer to a function returning void and
taking no arguments" as follows:
typedef void (*pfv)();
Next, declare another typedef for "pointer to a function returning
void and taking a pfv":
typedef void (*pf_taking_pfv) (pfv);
Now declaring an array of 10 such pointers is a breeze:
pf_taking_pfv p[10]; /*equivalent to
void (*p[10]) (void (*)()); but much more
readable*/
Tip 6: All About Pointers to Members
A class can have two general categories of members: function members
and data members. Likewise, there are two categories of pointers to
members: pointers to member functions and pointers to data members.
The latter are less common because in general, classes do not have
public data members. However, when using legacy C code that contains
structs or classes that happen to have public data members, pointers
to data members are useful.
Pointers to members are one of the most intricate syntactic constructs
in C++, and yet, they are a very powerful feature too. They enable you
to invoke a member function of an object without having to know the
name of that function. This is very handy implementing callbacks.
Similarly, you can use a pointer to data member to examine and alter
the value of a data member without knowing its name.
Pointers to Data Members
Although the syntax of pointers to members may seem a bit confusing at
first, it's consistent and resembles the form of ordinary pointers,
with the addition of the class name followed by the operator :: before
the asterisk. For example, if an ordinary pointer to int looks as
follows:
int * pi;
You define a pointer to an int member of class A as follows:
int A::*pmi; /* pmi is a pointer to an int
member of A*/
You initialize a pointer to member like this:
class A
{
public:
int num;
int x;
};
int A::*pmi = & A::num; /* 1 */
The statement numbered 1 declares a pointer to an int member of class
A and initializes it with the address of the member num. Using pmi and
the built-in operator .* you can examine and modify the value of num
in any object of class A:
A a1, a2;
int n=a1.*pmi; /* copy a1.num to n */
a1.*pmi=5; /* assign the value 5 to a1.num */
a2.*pmi=6; /* assign the value 6 to a2.num */
If you have a pointer to A, you need to use the built-in operator ->*
instead:
A * pa=new A;
int n=pa->*pmi;
pa->*pmi=5;
Pointers To Member Functions
These consist of the member function's return type, the class name
followed by ::, the pointer's name, and the function's parameter list.
For example, a pointer to a member function of class A that returns an
int and takes no arguments is defined as follows (note that both pairs
of parentheses are mandatory):
class A
{
public:
int func ();
};
int (A::*pmf) ();
In other words, pmf is a pointer to a member function of class A that
returns int and takes no arguments. In fact, a pointer to a member
functions looks as an ordinary pointer to function, except that it
also contains the class's name immediately followed by the ::
operator. You can invoke the member function to which pmf points using
operator .*:
pmf=&A::func;
A a;
(a.*pmf)(); /* invoke a.func() */
If you have a pointer to an object, you use the operator ->* instead:
A *pa=&a;
(pa->*pmf)(); /*calls pa->func() */
Pointers to member functions respect polymorphism. Thus, if you call a
virtual member function through such a pointer, the call will be
resolved dynamically. Note, however, that you can't take the address
of a class's constructor and destructor.
Tip 7: Avoiding Memory Fragmentation
Often, applications that are free from memory leaks but frequently
allocate and deallocate dynamic memory show gradual performance
degradation if they are kept running for long periods. Finally, they
crash. Why is this? Recurrent allocation and deallocation of dynamic
memory causes heap fragmentation, especially if the application
allocates small memory chunks. A fragmented heap might have many free
blocks, but these blocks are small and non-contiguous. To demonstrate
this, look at the following scheme that represents the system's heap.
Zeros indicate free memory blocks and ones indicate memory blocks that
are in use:
100101010000101010110
The above heap is highly fragmented. Allocating a memory block that
contains five units (i.e., five zeros) will fail, although the systems
has 12 free units in total. This is because the free memory isn't
contiguous. On the other hand, the following heap has less free memory
but it's not fragmented:
1111111111000000
What can you do to avoid heap fragmentation? First, use dynamic memory
as little as possible. In most cases, you can use static or automatic
storage or use STL containers. Secondly, try to allocate and
de-allocate large chunks rather than small ones. For example, instead
of allocating a single object, allocate an array of objects at once.
As a last resort, use a custom memory pool.
Tip 8: Don't Confuse Delete with Delete []
There's a common myth among programmers that it's OK to use delete
instead of delete [] to release arrays built-in types. For example,
int *p=new int[10];
delete p; /*bad; should be: delete[] p*/
This is totally wrong. The C++ standard specifically says that using
delete to release dynamically allocated arrays of any type yields
undefined behavior. The fact that on some platforms, applications that
use delete instead of delete [] don't crash can be attributed to sheer
luck: Visual C++, for example, implements both delete[] and delete for
built-in types by calling free(). However, there is no guarantee that
future releases of Visual C++ will adhere to this convention.
Furthermore, there's no guarantees that this code will work on other
compilers. To conclude, using delete instead of delete[] and vice
versa is hazardous and should be avoided.
Tip 9: Optimizing Class Member Alignment
The size of a class can be changed simply by playing with the order of
its members'
declaration:
struct A
{
bool a;
int b;
bool c;
}; /*sizeof (A) == 12*/
On my machine, sizeof (A) equals 12. This result might seem surprising
because the total size of A's members is only 6 bytes: 1+4+1 bytes.
Where did the remaining 6 bytes come from? The compiler inserted 3
padding bytes after each bool member to make it align on a four-byte
boundary. You can reduce A's size by reorganizing its data members as
follows:
struct B
{
bool a;
bool c;
int b;
}; // sizeof (B) == 8
This time, the compiler inserted only 2 padding bytes after the member
c. Because b occupies four bytes, it naturally aligns on a word
boundary without necessitating additional padding bytes.
Tip 10: Differences between Postfix and Prefix Operators
The built-in ++ and- operators can appear on both sides of their
operand:
int n=0;
++n; /*prefix*/
n++; /*postfix*/
You probably know that a prefix operator first changes its operand
before taking its value. For example:
int n=0, m=0;
n = ++m; /*first increment m, then assign its
value to n*/
cout << n << m; /* display 1 1*/
In this example, n equals 1 after the assignment because the increment
operation took place before m's value was taken and assigned to n. By
contrast,
int n=0, m=0;
n = m++; /*first assign m's value to n, then
increment m*/
cout << n << m; /*display 0 1*/
In this example, n equals 0 after the assignment because the increment
operation took place after m's original value was taken and assigned
to n.
To understand the difference between postfix and prefix operators
better, examine the disassembly code generated for these operations.
Even if you're not familiar with assembly languages, you can
immediately see the difference between the two; simply notice where
the inc (increment) assembly directive appears:
/*disassembly of the expression: m=n++;*/
mov ecx, [ebp-0x04] /*store n's value in ecx
register*/
mov [ebp-0x08], ecx /*assign value in ecx to m*/
inc dword ptr [ebp-0x04] /*increment n*/
/*disassembly of the expression: m=++n;*/
inc dword ptr [ebp-0x04] /*increment n;*/
mov eax, [ebp-0x04] /*store n's value in eax
register*/
mov [ebp-0x08], eax /*assign value in eax to m*/
Tip 11: Eliminating Temporary Objects
C++ creates temporary objects "behind your back" in several contexts.
The overhead of a temporary can be significant because both its
constructor and destructor are invoked. You can prevent the creation
of a temporary object in most cases, though. In the following example,
a temporary is created:
Complex x, y, z;
x=y+z; /* temporary created */
The expression y+z; results in a temporary object of type Complex that
stores the result of the addition. The temporary is then assigned to x
and destroyed subsequently. The generation of the temporary object can
be avoided in two ways:
Complex y,z;
Complex x=y+z; /* initialization instead of
assignment */
In the example above, the result of adding x and z is constructed
directly into the object x, thereby eliminating the intermediary
temporary. Alternatively, you can use += instead of + to get the same
effect:
/* instead of x = y+z; */
x=y;
x+=z;
Although the += version is less elegant, it costs only two member
function calls: assignment operator and operator +=. In contrast, the
use of + results in three member function calls: a constructor call
for the temporary, a copy constructor call for x, and a destructor
call for the temporary.
Tip 12: Why Inheriting from a Class That Has No Virtual Destructor is
Dangerous
Classes with a non-virtual destructor aren't meant to serve as base
classes (such classes are usually known as "concrete classes").
std::string, std::complex, and std::vector are concrete classes. Why
is inheriting from such classes not recommended? When you use public
inheritance, you create an is-a relationship between the base class
and its derived classes. Consequently, pointers and references to base
can actually point to a derived object. Because the destructor isn't
virtual, C++ will not call the entire destructor chain when you delete
such an object. For example:
class A
{
public:
~A() // non virtual
{
// ...
}
};
class B: public A /* bad; A has a non virtual
dtor*/
{
public:
~B()
{
// ...
}
};
int main()
{
A * p = new B; /*seemingly OK*/
delete p; /*trouble, B's dtor not called*/
}
The result of failing to invoke an object's destructor is undefined.
Therefore, you shouldn't use publicly inherit from such classes. In
particular, don't derive from STL containers and std::string, as
tempting as it may seem.
Tip 13: Declaring Nested Classes as Friends of Their Enclosing Class
When you declare a nested class as a friend of its containing class,
place the friend declaration after the declaration of the nested
class, not before it:
class A
{
private:
int i;
public:
class B /*nested class declared first*/
{
public:
B(A & a) { a.i=0;}; /*access A's private
member*/
};
friend class B;/*friend declaration at the
right place*/
};
If you place the friend declaration before the nested class's
declaration, the compiler will discard the declaration since the
friend class hasn't been seen yet.
Tip 14: Useful STL Terminology
Here are some key terms that you may find useful when reading Standard
Template Library (STL) literature and documentation.
Container
A container is an object that stores objects as its elements.
Normally, it's implemented as a class template that has member
functions for traversing, storing and removing elements. Examples of
container classes are std::list and std::vector.
Genericity
The quality of being generic, or type-independent. The above
definition of the term container is too loose because it may apply to
strings, arrays and structs, which also store objects. However, a real
container isn't limited to a specific data type or a set of types.
Rather, it can store any built-in and user-defined type. Such a
container is said to be generic. Note that a string can only contain
characters. Genericity is perhaps the most important characteristic of
STL. The third tip presents the standard base classes for function
objects. Since function objects are one of the constituents of generic
programming, adhering to standard conventions in their design and
implementation will save you a lot of difficulties.
Algorithm
A set of operations applied to a sequence of objects. Examples of
algorithms are std::sort(), std::copy(), and std::remove(). STL
algorithms are implemented as function templates taking iterators.
Adaptor
An adaptor is a special object that can be plugged to an exiting class
or function to change its behavior. For example, by plugging a special
adaptor to the std::sort() algorithm, you can control whether the
sorting order is descending or ascending. STL defines several kinds of
sequence adaptors, which transform a container to a different
container with a more restricted interface. A stack, for instance, is
often formed from queue<> and an adaptor that provides the necessary
push() and pop() operations.
Big Oh Notation
A special notation used in performance measurements of algorithms. The
STL specification imposes minimum performance limits on the operations
of its algorithms and container operations. An implementation may
offer better performance but not worse. The Big Oh notation enables
you to evaluate the efficiency of algorithms and containers for a
given operation and data structure. An algorithm such as std::find(),
which traverses every element is a sequence (in the worst case
scenario) has the following notation:
T(n) = O(n). /* linear complexity */
Iterator
An iterator is an object that functions as a generic pointer.
Iterators are used for traversing, adding and removing container
elements. STL defines five major categories of iterators:
input iterators and output iterators
forward iterators
bidirectional iterators
random access iterators
Note that this list doesn't represent inheritance relationships; it
merely describes the iterator categories and their interfaces. Each
lower category is a superset of the category above it. For instance, a
bidirectional iterator provides all the functionality of a forward
iterators plus additional functionality. Here is a brief summary of
the functionality and interfaces for these categories:
Input iterators allow an algorithm to advance the iterator and offer
read-only access to an element.
Output iterators allow an algorithm to advance the iterator and offer
write-only access to an element.
Forward iterators support both read and write access, but traversal is
permitted only in one direction.
Bidirectional iterators allow the user to traverse the sequence in
both directions.
Random access iterators support random jumps and "pointer arithmetic"
operations, for example:
string::iterator it = s.begin();
char c = *(it+5); /* assign fifth char to c*/
Tip 15: The Location of Template Definitions
Normally, you declare functions and classes in a .h file and place
their definition in a separate .cpp file. With templates, this
practice isn't really useful because the compiler must see the actual
definition (i.e., the body) of a template, not just its declaration,
when it instantiates a template. Therefore, it's best to place both
the template's declaration and definition in the same .h file. This is
why all STL header files contain template definitions.
In the future, when compilers support the "export" keyword, it will be
possible to use only the template's declaration and leave the
definition in a separate source file.
Tip 16: Standard Base Classes for Function Object
To simplify the process of writing function objects, the Standard
Library provides two class templates that serve as base classes of
user-defined function objects: std::unary_function and
std::binary_function. Both are declared in the header <functional>. As
the names suggest, unary_function serves as a base class of function
objects taking one argument and binary_function serves as a base class
of function objects taking two arguments. The definitions of these
base classes are as follows:
template < class Arg, class Res > struct
unary_function
{
typedef Arg argument_type;
typedef Res result_type;
};
template < class Arg, class Arg2, class Res >
struct binary_function
{
typedef Arg first_argument_type;
typedef Arg2 second_argument_type;
typedef Res result_type;
};
These templates don't provide any useful functionality. They merely
ensure that arguments and return values of their derived function
objects have uniform names. In the following example, the predicate
is_vowel, which takes one argument, inherits from unary_function:
template < class T >
class is_vowel: public unary_function< T, bool >
{
public:
bool operator ()(T t) const
{
if ((t=='a')||(t=='e')||(t=='i')||(t=='o')||(t=='u'))
return true;
return false;
}
};
Tip 17: Storing Dynamically Allocated Objects in STL Containers
Suppose you need to store objects of different types in the same
container. Usually, you do this by storing pointers to dynamically
allocated objects. However, instead of using named pointers, insert
the elements to the container as follows:
class Base {};
class Derived : public Base{};
std::vector <Base *> v;
v.push_back(new Derived);
v.push_back(new Base);
This way you ensure that the stored objects can only be accessed
through their container. Remember to delete the allocated objects as
follows:
delete v[0];
delete v[1];
Tip 18: Treating a Vector as an Array
Suppose you have a vector of int and function that takes int *. To
obtain the address of the internal array of the vector v and pass it
to the function, use the expressions &v[0] or &*v.front(). For
example:
void func(const int arr[], size_t length );
int main()
{
vector <int> vi;
//.. fill vi
func(&vi[0], vi.size());
}
It's safe to use &vi[0] and &*v.front() as the internal array's
address as long as you adhere to the following rules: First, func()
shouldn't access out-of-range array elements. Second, the elements
inside the vector must be contiguous. Although the C++ Standard
doesn't guarantee that yet, I'm not aware of any implementation that
doesn't use contiguous memory for vectors. Furthermore, this loophole
in the C++ Standard will be fixed soon.
Tip 19: Dynamic Multidimensional Arrays and Vectors
You can allocate multidimensional arrays manually, as in:
int (*ppi) [5]=new int[4][5]; /*parentheses
required*/
/*fill array..*/
ppi[0][0] = 65;
ppi[0][1] = 66;
ppi[0][2] = 67;
//..
delete [] ppi;
However, this style is tedious and error prone. You must parenthesize
ppi to ensure that the compiler parses the declaration correctly, and
you must delete the allocated memory. Worse yet, you can easily bump
into buffer overflows. Using a vector of vectors to simulate a
multidimensional array is a significantly superior alternative:
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector <vector <int> > v; /*two dimensions*/
v.push_back(vector <int>()); /*create v[0]*/
v.push_back(vector <int>()); /*create v[1]*/
v[0].push_back(15); /*assign v[0][0]*/
v[1].push_back(16); /*assign v[1][0]*/
}
Because vector overloads operator [], you can use the [][] notation as
if you were using a built-in two-dimensional array:
cout << v[0][0];
cout << v[1][0];
The main advantages of using a vector of vectors are two: vector
automatically allocates memory as needed. Secondly, it takes care of
deallocating memory so you don't have to worry about potential memory
leaks.
Tip 20: Why You Shouldn't Store auto_ptr Objects in STL Containers
The C++ Standard says that an STL element must be "copy-constructible"
and "assignable." These fancy terms basically mean that for a given
class, assigning and copying one object to another are well-behaved
operations. In particular, the state of the original object isn't
changed when you copy it to the target object.
This is not the case with auto_ptr, though: copying or assigning one
auto_ptr to another makes changes to the original in addition to the
expected changes in the copy. To be more specific, the original object
transfers ownership of the pointer to the target, thus making the
pointer in the original null. Imagine what would happen if you did
something like this:
std::vector <auto_ptr <Foo> > vf;/*a vector of
auto_ptr's*/
// ..fill vf
int g()
{
std::auto_ptr <Foo> temp=vf[0]; /*vf[0]
becomes null*/
}
When temp is initialized, the pointer of vf[0] becomes null. Any
attempt to use that element will cause a runtime crash. This situation
is likely to occur whenever you copy an element from the container.
Remember that even if your code doesn't perform any explicit copy or
assignment operations, many algorithms (std::swap(),
std::random_shuffle() etc.) create a temporary copy of one or more
container elements. Furthermore, certain member functions of the
container create a temporary copy of one or more elements, thereby
nullifying them. Any subsequent attempt to the container elements is
therefore undefined.
Visual C++ users often say that they have never encountered any
problems with using auto_ptr in STL containers. This is because the
auto_ptr implementation of Visual C++ (all versions thereof) is
outdated and relies on an obsolete specification. When the vendor
decides to catch up with the current ANSI/ISO C++ Standard and change
its Standard Library accordingly, code that uses auto_ptr in STL
containers will manifest serious malfunctions.
To conclude, you shouldn't use auto_ptr in STL containers. Use either
bare pointers or other smart pointer classes instead of auto_ptr (such
classes are available at www.Boost.org)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]
<iostream.h> has never been standard at all,
so it has never been *deprecated*.
> Tip 4: Calling a Function Before Program's Startup
>
> Certain applications need to invoke startup functions that run before
> the main program starts. For example, polling, billing, and logger
> functions must be invoked before the actual program begins. The
> easiest way to achieve this is by calling these functions from
> a constructor of a global object. Because global objects are
> conceptually constructed before the program's outset, these functions
> will run before main() starts. For example:
>
> [ Logger example snipped ]
>
> The global object log is constructed before main() starts. During its
> construction, log invokes the function activate_log(). Thus, when
> main() starts, it can read data from the log file.
Global objects may or may not be constructed before main() starts.
3.6.2/p3 says:
It is implementation-defined whether or not the dynamic
initialization of an object of namespace scope is done before the
first statement of main. If the initialization is deferred to
some point in time after the first statement of main, it shall
occur before the first use of any function or object defined in
the same translation unit as the object to be initialized.
> Tip 5: Hiding the Cumbersome Syntax of Pointers to Functions
>
> Can you tell what the following declaration means?
>
> void (*p[10]) (void (*)());
>
> p is an "array of 10 pointers to a function returning void and taking
> a pointer to another function that returns void and takes no
> arguments." The cumbersome syntax is nearly indecipherable, isn't it?
I guess this would be not so hard for many experienced programmers.
I find these much more complicated:
void (*signal(int sig, void (*handler)(int)))(int);
int (*foo(int (*[5])(int *)))(int *);
:-)
> typedef void (*pfv)();
> typedef void (*pf_taking_pfv) (pfv);
> pf_taking_pfv p[10];
Alternatively:
typedef void fv();
typedef void f_taking_pfv(fv*);
f_taking_pfv* p[10];
Some people prefer this because it doesn't hide pointers
inside typedef'ed names.
> Tip 9: Optimizing Class Member Alignment
>
> The size of a class can be changed simply by playing with the order of
> its members'
> declaration:
>
> struct A
> {
> bool a;
> int b;
> bool c;
> }; /*sizeof (A) == 12*/
>
> On my machine, sizeof (A) equals 12. This result might seem surprising
> because the total size of A's members is only 6 bytes: 1+4+1 bytes.
> Where did the remaining 6 bytes come from? The compiler inserted 3
> padding bytes after each bool member to make it align on a four-byte
> boundary. You can reduce A's size by reorganizing its data members as
> follows:
>
> struct B
> {
> bool a;
> bool c;
> int b;
> }; // sizeof (B) == 8
>
> This time, the compiler inserted only 2 padding bytes after the member
> c. Because b occupies four bytes, it naturally aligns on a word
> boundary without necessitating additional padding bytes.
This kind of optimization should be performed only when the size
of the class is critical. Changing the order of members also causes
changin their order of initialization, which might have significant
effects, so it should not be performed blindly.
> Tip 11: Eliminating Temporary Objects
>
> Complex y,z;
> Complex x=y+z; /* initialization instead of
> assignment */
>
> In the example above, the result of adding x and z is constructed
> directly into the object x, thereby eliminating the intermediary
> temporary.
Is it guaranteed? I guess not.
It is possible that a temporary is created and x is copy-constructed
from that temporary.
--
KIM Seungbeom <musi...@bawi.org>
This part is incorrect. VC5 and VC6 include a non-standard auto_ptr (not
surprising since they were produced before the standard). VC7 and VC7.1
include a fully conformant auto_ptr. VC4.2 and before do not include STL at
all (although IIRC, VC4.2 included the original HP implementation of the STL
as an unsupported goodie).
-cd
> > Tip 5: Hiding the Cumbersome Syntax of Pointers to Functions
> > Can you tell what the following declaration means?
> > void (*p[10]) (void (*)());
[...]
> > typedef void (*pfv)();
> > typedef void (*pf_taking_pfv) (pfv);
> > pf_taking_pfv p[10];
> Alternatively:
> typedef void fv();
> typedef void f_taking_pfv(fv*);
> f_taking_pfv* p[10];
> Some people prefer this because it doesn't hide pointers
> inside typedef'ed names.
The second line could also be written
typedef void f_taking_pfv(fv);
This shows that the pointers may be hidden within implicit
conversions using your style. Even more interesting within
a function.
void mess (fv /* a pointer */ p) {
fv /* a function declaration */ q;
q = p; // error
}
Let's face it, no matter how you try to hide the garbage in typedefs,
it still stinks.
John
Well they should fix up the list they have first.
> Tip 1: <iostream.h> or <iostream>?
>
> First, the .h notation of standard header files was
> deprecated more than five years ago. Using deprecated features in new
> code is never a good idea. In terms of functionality,
iostream.h is not even deprecated (and I have not clue what event happend
5 years ago they are referring to). Iostream.h is not and never has been
a standard C++ header period. You get into all kinds of grief (especially
if you try to mix with standard code) using the nonstandard header.
>
> Tip 2: Binding a Reference to an Rvalue
> an rvalue is an expression that cannot appear on the
> left-hand side of an assignment expression.
Bullshit. This has never been the definition of lvalue and rvalue.
Plenty of lvalues can't appear on the left side of an assignment
either, and do to operator overloading, some rvalues can be the
target of assignments as well.
>
> Tip 3: Comma-Separated Expressions
>
> Comma-separated expressions were inherited from C. It's likely that
> you use such expressions in for- and while-loops rather often. Yet,
> the language rules in this regard are far from being intuitive. First,
> let's see what a comma separated expression is.
>
It's not even clear what the tip here is? Are they trying to promote the
use of the comma operator? Just what are they up to.
>
> Tip 6: All About Pointers to Members
This isn't all about pointer to members. They could have talked about
conversions between pointers to members which is perhaps the most
misunderstood feature of them.
>
> Tip 7: Avoiding Memory Fragmentation
>
> What can you do to avoid heap fragmentation? First, use dynamic memory
> as little as possible. In most cases, you can use static or automatic
> storage or use STL containers.
Much as I advocate using STL containers for a number of reasons, memory
fragmentation isn't one of them. A vector or string dynamically allocates it's
internal storage.
> Tip 10: Differences between Postfix and Prefix Operators
> You probably know that a prefix operator first changes its operand
> before taking its value. For example:
>
> int n=0, m=0;
> n = ++m; /*first increment m, then assign its
> value to n*/
> cout << n << m; /* display 1 1*/
>
> In this example, n equals 1 after the assignment because the increment
> operation took place before m's value was taken and assigned to n. By
> contrast,
Builshit, these guys clearly don't understand what a side effect is.
>
> Tip 12: Why Inheriting from a Class That Has No Virtual Destructor is
> Dangerous
>
> Classes with a non-virtual destructor aren't meant to serve as base
> classes (such classes are usually known as "concrete classes").
No, that's not the definition of "concrete class" either. What are these
people smoking.
/
> Tip 1: <iostream.h> or <iostream>?
> Tip 2: Binding a Reference to an Rvalue
> Tip 3: Comma-Separated Expressions
> Tip 4: Calling a Function Before Program's Startup
> Tip 5: Hiding the Cumbersome Syntax of Pointers to Functions
> Tip 6: All About Pointers to Members
> Tip 7: Avoiding Memory Fragmentation
> Tip 8: Don't Confuse Delete with Delete []
> Tip 9: Optimizing Class Member Alignment
> Tip 10: Differences between Postfix and Prefix Operators
> Tip 11: Eliminating Temporary Objects
> Tip 12: Why Inheriting from a Class That Has No Virtual Destructor is
> Dangerous
> Tip 13: Declaring Nested Classes as Friends of Their Enclosing Class
> Tip 14: Useful STL Terminology
> Tip 15: The Location of Template Definitions
> Tip 16: Standard Base Classes for Function Object
> Tip 17: Storing Dynamically Allocated Objects in STL Containers
> Tip 18: Treating a Vector as an Array
> Tip 19: Dynamic Multidimensional Arrays and Vectors
> Tip 20: Why You Shouldn't Store auto_ptr Objects in STL Containers
If these are supposed to be the top twenty, it's a little ridiculous. I
can't really believe that anyone would put using prefix rather than
postfix or optimizing class member alignment on the same level as, say,
RAII (which he doesn't mention) or inheriting from a class without a
virtual destructor.
There are things in C++ which are serious mistakes. He doesn't mention
one, but perhaps that isn't the goal here. (Trying to store auto_ptr in
an STL container would be one, except that you should get an error from
the compiler if you try.)
There are things which are very dangerous, and to which one must pay
particular attention. Deriving from a class without a virtual
destructor would belong here: although there are cases where it is
appropriate, it's not the sort of thing you should do without thinking.
But, for example, providing a non-trivial destructor and an assignment
operator without providing a copy constructor should also ring a bell.
Not mentionned.
There are techniques which make otherwise difficult problems much
simpler. RAII is an excellent example, as is the swap idiom for
assignment. These are the sort of things I'd most expect from a list of
tips, but I don't really see any here.
Then there are a lot of trivia, like the choice of pre- or
post-incrementation, which don't really make any difference in practice
(at least according to my benchmarks), but which everyone likes to talk
about.
On the whole, not much really interesting.
--
James Kanze mailto:jka...@caicheuvreux.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
> > > Tip 5: Hiding the Cumbersome Syntax of Pointers to Functions
> > > Can you tell what the following declaration means?
> > > void (*p[10]) (void (*)());
> [...]
> > > typedef void (*pfv)();
> > > typedef void (*pf_taking_pfv) (pfv);
> > > pf_taking_pfv p[10];
> > Alternatively:
> > typedef void fv();
> > typedef void f_taking_pfv(fv*);
> > f_taking_pfv* p[10];
> > Some people prefer this because it doesn't hide pointers inside
> > typedef'ed names.
> The second line could also be written
> typedef void f_taking_pfv(fv);
> This shows that the pointers may be hidden within implicit conversions
> using your style. Even more interesting within a function.
> void mess (fv /* a pointer */ p) {
> fv /* a function declaration */ q;
> q = p; // error
> }
> Let's face it, no matter how you try to hide the garbage in typedefs,
> it still stinks.
More significantly, the user always has to know the real type in order
to use it. You may be able to hide something like:
int (MyClass::*table[])( void (YourClass::*)() ) = { ... } ;
with a typedef, but you're still stuck with:
result = (this->*(table[ i ]))( &YourClass::whatever ) ;
when you want to use it.
In the end, I find such typedef's confusing and distracting. When I use
a typedef, it is to give a semantic meaning to the type, not to pretend
to hide complexity. And the typedef will almost never refer to another
typedef.
My experience is that my readers appreciate this as well. Typically,
when they see something like the above, they know immediately that the
type is complicated, and that it will take some thinking about when they
want to use it.
(My experience is also that it is generally better to wrap such pointers
in a functional object, in order to make both the declaration and the
use easier:-).)
--
James Kanze mailto:jka...@caicheuvreux.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
What actually happens when you request memory is system dependent. I
know that on at least one system (PalmOs) that the OS can just defrag it
for you automatically if it needs to.
I would also argue that there is plenty of memory around for most apps.
So this would only apply to certain apps anyway. Also this assumes
that you don't need to use free store and that the stack (where the
automatic varibables go) is limitless (again not true on PalmOs and
probably others).
So no since this doesn't apply to most people, its not a top 20 tip.
What's more, there's no mention on the list of static order of
initialization, which often goes along with suggestions such as
this one. As soon as you have two static objects interdependent,
you have initialization problems to address. Why wasn't that on
the list?
> > Tip 5: Hiding the Cumbersome Syntax of Pointers to Functions
> >
> > Can you tell what the following declaration means?
> >
> > void (*p[10]) (void (*)());
> >
> > p is an "array of 10 pointers to a function returning void
and taking
> > a pointer to another function that returns void and takes no
> > arguments." The cumbersome syntax is nearly indecipherable,
isn't it?
>
> I guess this would be not so hard for many experienced
programmers.
I agree. The declaration wasn't hard at all.
> I find these much more complicated:
>
> void (*signal(int sig, void (*handler)(int)))(int);
> int (*foo(int (*[5])(int *)))(int *);
Even those aren't that difficult. The hardest part is matching
the parentheses to make sure you grok it correctly.
> > typedef void (*pfv)();
> > typedef void (*pf_taking_pfv) (pfv);
> > pf_taking_pfv p[10];
>
> Alternatively:
>
> typedef void fv();
> typedef void f_taking_pfv(fv*);
> f_taking_pfv* p[10];
>
> Some people prefer this because it doesn't hide pointers
> inside typedef'ed names.
It's interesting. I've never done it that way -- didn't even know
you could for the longest time -- and now I'm glad. You can't do
that with member function pointers, so I'd rather keep such type
declarations consistent in my code.
> > Tip 9: Optimizing Class Member Alignment
This certainly doesn't warrant being on a top 20 list.
> > The size of a class can be changed simply by playing with
the order of
> > its members'
> > declaration:
[snip example with bool, int, bool]
> > You can reduce A's size by reorganizing its data members as
> > follows:
[snip example with bool, bool, int]
> > This time, the compiler inserted only 2 padding bytes after
the member
> > c. Because b occupies four bytes, it naturally aligns on a
word
> > boundary without necessitating additional padding bytes.
The bool, bool, int version would still require padding if the
platform uses four byte boundaries and bools only take one byte.
Declaring it int, bool, bool, means that the bools are
automatically aligned (no padding), and no padding is required
after the bools because there are no more data members to
address. Thus, the object size is reduced another two bytes.
This leads to the style of putting the smallest types last in
declaration order. If you do that as a matter of course, you
hardly think about it and you get reasonably good packing.
> This kind of optimization should be performed only when the
size
> of the class is critical. Changing the order of members also
causes
Following the guideline I gave above gives you good packing all
of the time. If size is ever critical, you can then look at
actual dm sizes and, armed with alignment requirements of your
platform, hand pack the structure optimally. That effort should
only be undertaken when critical.
> changin their order of initialization, which might have
significant
> effects, so it should not be performed blindly.
While I agree with you in principle, order of initialization is
rarely an issue. When it is, the declarations should be commented
to indicate the required ordering. When it isn't, you can just
follow the guideline above as your default style and get good
enough packing.
--
Rob
To reply, change nothing to bigfoot in the address shown.
That is a false conclusion. The size of every type must meet the
requirements that an array of that type is contiguous (no inter-element
padding). If your int must be on a four-byte boundary then the type must
occupy a multiple of four bytes.
--
ACCU Spring Conference 2003 April 2-5
The Conference you cannot afford to miss
Check the details: http://www.accuconference.co.uk/
Francis Glassborow ACCU
Objects can have padding at the end, and the object size
includes the padding at the end of the object.
struct X { int a; char b; };
On many systems with integer alignment of 4 bytes,
sizeof(X) is 8 due to the padding of 3 bytes at the end.
--
KIM Seungbeom <musi...@bawi.org>
So it was. Given your counter, I decided to read about it.
5.3.3/2 indeed says that. Apparently my experience with platforms
that don't require alignment crept back in. Thanks for bringing
it to my attention.
--
Rob
To reply, change nothing to bigfoot in the address shown.
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
Er, what exactly is wrong with that one? My money is on it printing "11".
Are you reffering to the undefined behavior of something like:
n = m++ + m++;
?
But at least for object for which it may be interesting to optimize
size, if
we uses essentially decresing size order, part of the padding may be
reused
in some cases:
struct Y : X { char c; }; // Would typically also be 8 bytes with
interger alignment of 4.
struct Z { X x; char d; }; // Idem
In pratice for "data" oriented classes, I prefer to put double at the
beggining, then structures, pointers and integers and finally put small
type
at the end (short, bool, char).
Also using reverse size order make the size less alignment dependant and
typically better aligned if data is packed. This may help port code if
we
have some depedencies on the format (for ex. loading a structure as a
block
from disk) if everything is properly done from the start (and it will
avoid
wasting space).
I know, with good design, we won't have such depedencies... but in real
life, we have far too much such depedencies in our code.
If the size dio matter, I really suggest to either uses a pragma (if
available) or compile-time assertion to validate that we have the
expected
size. At least if something is modified, it will not compile and also it
help ensure that we get expected size (not too much wasting) when
defining
new structures. If lot of these structure are kept in memory, a gain in
size
may be noticeable on memory requirment of the application.
I have a problem understanding the swap idiom.
In all the examples that I saw, a temporary is created, swapped with
the object and then discarded.
My question is, if the temporary is discarded anyway, why swap instead
of copy?
Because the copy constructor might throw an exception. When you create
the temporary first, if that happens the original objects are
unaffected. If you copy directly then you may have a partially copied
object, half new object and half old object. Then you can have long
discussions about who can do what and with which and to whom.
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
The purpose of swap is to provide the strong exception guarantee. The
copy-constructor and an assignment-operator might throw an exception,
but a swap would normally be exception-safe. It is in these
circumstances that the swap idiom is useful.
Consider:
class A;
class B;
// A and B are unknown in this context, but assume each has an
// assignment-operator that may throw an exception.
// A Standard container is one example of such a class.
class Fragile
{
A a;
B b;
Fragile& operator=(const Fragile& rhs);
};
Now watch this implementation of operator=:
Fragile& Fragile::operator=(const Fragile& rhs)
{
a = rhs.a;
// danger!!
b = rhs.b;
}
The second assignment is dangerous since - if it throws - you end up
with a half-copied Fragile element.
Assuming swap is a no throw operation (in practice I guess it will
almost always be so), this implementation is safe:
Fragile& Fragile::operator=(const Fragile& rhs)
{
Fragile tmp(rhs);
std::swap(*this,tmp);
}
If the copyconstructor throws, the object assigned to will not be
affected.
Kind regards
Peter
> I have a problem understanding the swap idiom. In all the examples
> that I saw, a temporary is created, swapped with the object and then
> discarded. My question is, if the temporary is discarded anyway, why
> swap instead of copy?
Because swap generally cannot throw; the same cannot be said of copy.
(In such cases, swap is also generally more efficient than copy. But
the real argument concerns correctness.)
The essential point of the swap idiom is that I make a complete copy
before modifying anything in the destination class. If making a copy
causes an exception (bad_alloc, etc.), then I've not modified the target
class, and all's well with the world. Once I have the complete copy, I
swap it with the destination data, swapping pointers instead of the
pointed to values whenever possible, and thus avoiding any new
allocations, or anything else that can throw. Having done this, the
temporary which previously held the copy holds all of the original
values; on leaving operator=, it is destructed, which frees up whatever
needed to be freed up.
The swap idiom isn't something that you apply blindly. If the objects
in question are complex, with a copy constructor which might throw, and
there is no specialization of std::swap, then the swap idiom doesn't
work. My rule of thumb is to use it if and only if 1) all of the
elements are either built-in types or have a member swap function, and
2) there is at least one pointer or user defined type. Without the
first condition, its not sure that the swap idiom will work -- in fact,
it may be impossible to write a correct assignment operator for the
class. And without the second, the swap idiom does double the work for
nothing.
Note that in many cases, unless the class is excessively simple, you
will want to use the compilation firewall idiom anyway. And using the
swap idiom with the compilation firewall idiom is a natural, since you
only swap a single pointer.
--
James Kanze mailto:jka...@caicheuvreux.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
I may have not been clear. I understand the idea of using the
temporary, I just don't understand why a swap operation (Temp <-->
Obj) is used on the temporary instead of a copy (Temp --> Obj).
Because copying might throw an exception.
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
To ensure proper destruction of the replaced object.
When a swap operation is used, Temp takes the replaced object
and destroys it when Temp goes out of scope. If a copy operation
were used, the replaced object would simply be lost and not be
properly destroyed, causing resource leak.
This applies only to situations where the class holds some resources,
e.g. where compilation firewalls (pImpls) are used. When the data
members don't hold any resources (such as plain integer),
there's no problem with copying.
--
KIM Seungbeom <musi...@bawi.org>
Here's the definition of std::swap in my implementation:
template<class _Ty> inline
void swap(_Ty& _X, _Ty& _Y)
{ _Ty _Tmp = _X; _X = _Y, _Y = _Tmp; }
If I define a copy operation like this:
template<class _Ty> inline
void MyCopy(_Ty& _X, _Ty& _Y)
{ _Y = _X; }
Please explain why it might throw while the swap may not.
That swap might throw, but that's the wrong swap. :)
Here's a contrived example of what we're discussing:
class String {
public:
// Construct a String from a NTBS
// @throw std::bad_alloc - memory could not be allocated
String (const char *rhs = "")
: len(strlen(rhs)),
buf(strcpy(new char[len + 1], rhs)) {
}
// Copy a String
// @throw std::bad_alloc - memory could not be allocated
String (const String &rhs)
: len(rhs.len),
buf(strcpy(new char[len + 1], rhs.buf)) {
}
// Assign to one String from another
// @throw std::bad_alloc - copy could not be allocated
String &operator= (const String &rhs) {
String(rhs).swap(*this);
return *this;
}
// Swap two Strings
void swap (String &s) throw () {
std::swap(len, s.len);
std::swap(buf, s.buf);
}
// Destroy a String
~String () {
delete[] buf;
}
private:
int len;
char *buf;
};
class TwoString {
public:
// Copy a TwoString
// @throw std::bad_alloc - memory could not be allocated
TwoString &operator= (const TwoString &rhs) {
TwoString(rhs).swap(*this);
return *this;
}
// Swap two TwoStrings
void swap (TwoString &rhs) throw () {
field1.swap(rhs.field1);
field2.swap(rhs.field2);
}
private:
String field1, field2;
};
First, note that both classes provide swap methods that do not throw;
String::swap simply swaps pointers and integers, and TwoString::swap
just calls String::swap, which does not throw. This logic scales well
beyond this example, since any swap operation built from operations
which do not throw also will not throw, ad infinitum.
From there, this is just a simple case of an atomic write. (x = y)
copies y, then swaps it with x. If the act of copying y fails, x is
left unchanged; if not, the whole operation succeeds. Then, the copy
goes out of scope, taking the original value of x with it, and x
leaves the expression with its new value.
To appreciate the simple perfection of this idiom, you must understand
the imperfections of the alternatives. Two examples:
TwoString &TwoString::operator= (const TwoString &rhs) {
field1 = rhs.field1;
field2 = rhs.field2;
return *this;
}
If the second assignment fails, the TwoString object is left in an
inconsistent state (half of the TwoString object was changed, half
not). Similarly:
TwoString &TwoString::operator= (const TwoString &rhs) {
if (this != &rhs) {
this->~TwoString();
new(this) TwoString(rhs);
}
return *this;
}
This is catastrophically worse; should the copy construction fail, the
object is not only inconsistent, but potentially uninitialized!
There really is only one other algorithm which is as safe as
copy-swap:
TwoString &TwoString::operator= (const TwoString &rhs) {
// copy all the fields of rhs
String rhs_field1(rhs.field1);
String rhs_field2(rhs.field2);
// swap my fields with the copies
field1.swap(rhs_field1);
field2.swap(rhs_field2);
return *this;
}
Look closely -- it's just copy-swap in different clothing! Moreover,
it's more verbose, and the additional verbiage adds nothing but room
for error.
In sum, the copy-swap idiom is *the* way to do exception-safe
assignment.
- Shane
For some types swap can be implemented so that it won't throw
exceptions. In general, though, it can't.
--
Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
For basic types (eg int, double, pointers) neither the std::swap
nor your copy will throw.
However, for classes things may get a little different, because either
the copy constructor or the assignment operator may fail (eg if
they allocate memory, that memory allocation may result in an
exception being thrown).
The swap idiom is based on an assumption that std::swap (or whatever
other function you use to do a swap in place) does not throw.
So std::swap must be specialised to ensure it doesn't throw.