Saturday, May 31, 2008

C++ #18

71. Returning a void Expression from a void Function
Examine this code snippet. Will your compiler accept it?

void a_func_returning_void();
void func()
{
return a_func_returning_void(); //returning a value from a void function?
}
At present, most compilers will not accept this code although it's perfectly legal according to the ANSI/ISO C++ standard. The problem is that until recently, returning an expression from a function that returns void was an error, even if the returned expression itself was void (as in the example above). This restriction was changed so a function with a void return type may return an expression that evaluates to void. This change was required to enable better support of generic algorithms in the Standard Template Library.
72. Limitations on the Location of a Default Arguments Declaration
Default arguments shall be specified only in the parameter-declaration-clause of a function declaration or in a template-parameter. This means that default arguments cannot appear in declarations of pointers to functions, references to functions, or typedef declarations:

void f( int n = 0); //OK
//the following declarations are all illegal
void (&rf) (int n = 0) = f; //reference to function
void (*pf) (int n = 0) ; //pointer to function
typedef void (*pfi) (int n = 0); //typedef
73. Avoid Passing Arguments With Side Effects to C Runtime Library Functions
Many of the C Runtime Library functions are in fact macros in disguise. < stdlib.h> routines, memset(), strcpy() and others are often implemented as macros that perform some low-level system call. While Standard C++ strictly forbids this macro in function disguise convention, it is still widely used in C.
You should beware of passing arguments with a side-effect to such pseudo-functions, because the results may be undefined. (A side effect is defined as a "change in the state of the execution environment." Modifying an object, accessing a volatile object, invoking an I/O function, or calling a function that does any of these operations are all side effects.) To see the potential dangers of passing an argument with a side effect to a macro in disguise, consider the following example:

char buff[12];
int n = 0;
/* second argument has a side-effect; bad idea */
memset(buff, ++n, sizeof(buff);
If memset() happens to be a macro that passes its arguments to another function, the value of n may be incremented twice before it is used.
74. When Is It Safe to Use Inline?
Excessive use of inline functions might bloat the size of the executable and reduce execution speed. In other words, using inline abundantly can degrade rather than enhance performance. For this reason, many programmers avoid function inlining altogether because they can't predict how inlining will affect performance. However, there is one guarantee: very short functions (e.g., a function that merely calls another function or a function that returns a data member of its object) are always good candidates for inlining. For example:

inline bool Foo::is_connection_alive()
{
return ::connection_alive(); // calls an API function
}
inline int Bar::get_x()
{
return x; // merely returns a data member
}
By inlining such functions, the size of the executable decreases slightly and execution speed is boosted. Note, however, that this rule applies to very short functions exclusively. For larger functions, you usually need to profile the software with and without inline to assess the effects of inlining.
75. Functions Are Extern by Default
Unless explicitly declared static, an ordinary function is implicitly declared extern in C and C++. For example:

extern void func(int i); // extern is redundant
However, sometimes the extern qualifier is added to a function declaration to document the fact that it has external linkage and can be called from any module, or source file, for example:

// from <string.h>
extern size_t strlen(const char * s);
Do not confuse plain extern with extern "C"—the latter has a special meaning, i.e., the identifier has C linkage rather than C++ linkage.
76. Declaring a Volatile Member Function
You're probably familiar with const member functions such as:

class A
{
public:
int get_x() const; // can't change A's state
};
C++ also supports volatile member functions. You declare a member function with the volatile specifier to ensure that it can be called safely for a volatile object:

class B
{
int x;
public:
void f() volatile; // volatile member function
};

int main()
{
volatile B b; // b is a volatile object
b.f(); // call a volatile member function safely
}
The object b is declared volatile. Calling a non-volatile member function from this object is unsafe, because b's state might have been changed by a different thread in the meantime. To ensure that f() can be called safely for a volatile object, it's declared volatile too.
77. extern "C" Declarations of static Member Functions

One of the issues that have been discussed at the C++ standards committee is whether the declaration of static member functions is permitted. For example:

struct A
{
extern "C" static void f(int); // is it allowed?
};

The C++ standard doesn't allow member functions, static or not, to be declared extern "C". This decision may seem arbitrary at first because you treat pointers to static member functions as pointers to extern (i.e., non-class) functions:

void (*pf) (int) = &A::f; // OK

Still, there is a good reason why you can't declare static member functions extern "C" -- name mangling. Name mangling applies to static member functions just as it applies to nonstatic member functions. It enables you to declare two or more static member functions with identical names in different classes without having their names conflict:

struct A
{
static void f(int);
};
struct B
{
static void f(int); // doesn't clash with A::f
};

An extern "C" declaration disables name mangling; if it were allowed, static member functions with identical names in different classes would conflict with each other.
78. Inheritance and a Reference to a Base Pointer
A reader posted this question on one of the C++ newsgroups. Assuming B is a base class for D, his program looks like this:

void g (B* p) ;
void f (B*& p); // modifies p

int main ()
{
D* p = NULL ;
g (p) ; // fine
f (p) ; /* error: "cannot convert parameter 1 from 'class D *' to
'class B *& ' a reference that is not to 'const' cannot
be bound to a non-lvalue" */
}
Function f() works as expected, but g() causes a compilation error. The reader wondered what was wrong with the invocation of f() in main(). Can you see what is wrong with it? p is a D*, not a B*, and to convert it to a B*, the implementation creates a temporary pointer (recall that a reference must be bound to a valid object; in this case, a pointer). Now because temporaries are rvalues, and you can't bind an rvalue to a non-const reference, the compiler complains. Declaring f() as follows:

void f (D* & p) { /* modifies p */ }
solves this problem.
79. Beware of Object Slicing
Passing a derived object by value to a function that takes a base object by value may cause a problem known as "object slicing"; every additional data member declared in the derived object is omitted. The resultant object contains only the data members declared in the base class. Furthermore, the dynamic binding mechanism is inoperative in this case:

class Date
{
private:
int year, month, day;
public:
virtual void Display() const; //output mm-dd-yy

};
class DateTime: public Date
{
private:
int hrs, min, sec; // additional members; might be sliced off
public:
void Display() const; // output mm-dd-yy hh:mm:ss
};

void f(Date b) // pass by value
{
b.Display(); // no dynamic binding; calls Date::Display()
}

int main()
{
DateTime dt;
f(dt); // dt is sliced
}
Object slicing may result in undefined behavior. Therefore, you should avoid passing an object by value when possible.

No comments: