Saturday, May 31, 2008

C++ #26

173. Exceptions During Object Construction Don't Cause Memory Leaks
Operator new performs two operations: it allocates memory from the free store, and it constructs an object on the allocated memory. The allocated memory doesn't leak when an exception is thrown during the construction process. The memory is returned to the free store by the system before the exception propagates to the program. Thus, an invocation of operator new can be construed as two consecutive operations. The first operation merely allocates a sufficient memory block from the free store. If this operation is successful, the second one begins, which consists of invoking the object's constructor on the memory address retained in the previous step. If an exception occurs during this operation, the previously allocated memory is automatically released, and only then does the exception propagate to the next scope.
174. Prefix Versus Postfix Operators
You can use both -- and ++ as prefix and postfix operators. When applied to primitives such as int or char, they are indistinguishable in terms of efficiency. When applied to objects, on the other hand, the overloaded prefix operators are significantly more efficient than the postfix ones. Therefore, whenever you're free to choose between postfix or prefix operators of an object, you should prefer the latter:

Void f() {
Date d1, d2;
d1++; d2-- //inefficient: postfix operator
++d1; --d2; //prefix is more efficient
The reason is that the postfix version usually involves a construction of an extra copy of the object, in which its state is preserved, whereas the prefix version is applied directly to its object:

class Date
Date operator++(int unused) { // Date++ is less efficient
Date temp(*this); //create a copy of the current object
this.AddDays(1); //increment current object
return temp; //return by value a copy of the object before it was incremented
Date& operator++() { // ++Date is more efficient
this.AddDays(1); //increment current object
return *this; //return by reference the current object
175. Overloading a Member Function Across Class Boundaries
Since a class is a namespace, the scope for overloading a member function is confined to the class containing this function. Sometimes, it is necessary to overload the same function in its class as well as in a class derived from it. Using an identical name in a derived class merely hides the base class' function, rather than overloading it:

class B {
public: void func();
class D : public B {
public: void func(int n); //now hiding B::f, not overloading it
D d;
d.func();//compilation error. B::f is invisible in d;
d.func(1); //OK, D::func takes an argument of type int
In order to overload (rather than hide) a function of a base class, you must inject it explicitly into the namespace of the derived class like this:

class D : public B {
using B::func; // inject the name of a base member function into the scope of D
public: void func(int n); // D now has two overloaded versions of func()
D d;
d.func ( ); // OK
d.func ( 10 ); // OK
176. Structs and Unions

It is common knowledge that the only difference between a struct and a union in C is that all elements of a union share the same memory location but the elements of struct do not. In C++, a struct is very similar to a class, the only difference being that the default access specifier for a struct is public and for a class it's private. This feature of the struct construct broadens the difference between a struct and a union in C++.

Firstly, a struct can be part of an inheritance hierarchy, a union cannot. In other words, a union can neither inherit nor be the parent of another union, struct, or class. This gives rise to another difference: a struct can have virtual members, while a union cannot. This means no static variables, or any objects that overload the = operator, can be members of a union. Finally, no object can be a member of a union if the object has a constructor or a destructor function, but no such restrictions apply to a struct.
177. Violating an Exception Specification
The implementation ensures that functions having an exception specification should throw only exceptions that are listed in their exception specification. For example:

class X{};
class Y{};

void f() throw X
throw Y(); // violation of exception specification

By default, an attempt to throw an exception of a type that is not listed in the exception specification causes the std::unexpected() function to be called. std::unexpected() in turn calls terminate(), which terminates the program.

No comments: