Table of Contents

Enums, Consts, Inlines & Macros

Pros and Cons

Macros

(e.g. #define C 1.5)

When should we use macros? (Not often)

When should we not use macros? (Most of the time)

template <typename T>
inline void max(const T& a, const T& b){
    return f(a > b ? a : b);
}

Consts

(e.g. const int x = 3;)

When should we use consts?

When should we not use constants?


Enums

(e.g. enum { x = 3 };)

When should we use enums?


Use Const’s Often

Here are some ways to use them:

const Variables

C-Style

char *p; // non const ptr, data
const char *p; // non-const pointer, const data
char * const p; // const pointer, non-const data
const char * const p; // const pointer, data

Rule of thumb:

C++97+ Style

typedef std::vector<int> v;

const v::iterator iter <= const pointer, non-const data

v::const_iterator iter <= non-const pointer, const data

Rule of thumb:

Example

const Rational operator*(const Rational& lhs, const Rational& rhs);

Means constant Rational returned from the * operator. Why does the result need to be constant? Because we could avoid:

(a*b) = c;

const Functions

const functions are useful for the purpose of overloading:

class Bar{
    const char& foo() const; //const function, returns const char&.
    
    char& foo(); //function, returns char&
};

const functions inside classes cannot change the class members. By creating a class Foo f, we can call the non-const version of foo(), meanwhile const Foo f uses the const foo().

A way to allow for reduced code duplication between const and non-const equivalents, one can do the following:

class Bar{
private:
    char c;
public:
    const char& foo() const{
        return c;
    }
    char& foo(){
        /* Reuse code from foo() const: */
        // removes const'ness
        return const_cast<char&>( 
            // calls foo() const
            static_cast<const Bar&>(*this).foo() 
        );
    }
};

Physical/Bitwise const vs. Logical const

Physical:

Logical(Recommended):

Initialize Before You Use

For non-member, built-in types, manually initialize:

int x = 0; // manual
char* cs = "hello world!"; // manual

For anything else, the onus is on the constructor.

Initialization vs. Assignments

/* Example of ASSIGNMENTS */
Foo::Foo(int x, char* s){
    // Assigned DURING constructor
    x_ = x;
    s_ = s;
}

Here, it’s using the assignment operator =, to call the copy constructor. It calls this function: Foo& operator=(const Foo& rhs) { ... ; return *this; }

/* Example of INITIALIZATION */
Foo::Foo(int x, char* s) : 
x_(x), s_(s)
{
    // Initialization BEFORE constructor
}

Here, it’s directly passing in x and s as arguments of the copy constructors, which is this: Foo::Foo(const Foo& rhs) : ... { ... }

The crucial difference here is that assignments call the constructor of those objects first, and then we re-assign. This is a waste of resources compared to initialization with the arguments.

In addition, const variables cannot be assigned. They must be initialized, forcing them to have a value inside the initializer list.

Issue with static & Singleton Design

If you have two files compiled in 2 different .so files like:

//f1.cpp
class Foo{
...
}

extern Foo foo; // allows any other cpp file to use it. Static non-local.
//f2.cpp
class Bar{
int x = foo.num();
...
}

Bar bar(); // uses foo.num()! Static non-local.

We have an issue here. We don’t know which order these objects are loaded.

Thus, we should implicitly define an order. We obviously want foo to exist when we call foo.num(), so we should define it in a static function:

//f1.cpp
Foo& foo(){ // Remind you of singleton by any chance?
    static Foo foo;
    return foo;
}
//f2.cpp
...
int x = foo().num();

This way, calling the function will be first, then we will be forced to create foo before bar is initialized statically.