(e.g. #define C 1.5
)
When should we use macros? (Not often)
When should we not use macros? (Most of the time)
#define MAX(a, b) f((a) > (b) ? (a) : (b))
MAX(++a, b)
, a
could be incremented twice!template <typename T>
inline void max(const T& a, const T& b){
return f(a > b ? a : b);
}
(e.g. const int x = 3;
)
When should we use consts?
When should we not use constants?
(e.g. enum { x = 3 };)
When should we use enums?
const
’s.Const
’s OftenHere are some ways to use them:
const
Variableschar *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:
const
before the star modifies the type.const
after the star modifies the pointer.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:
const
iterator modifies the pointer.const_iterator
modifies the data.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
Functionsconst
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()
);
}
};
const
vs. Logical const
Physical:
Logical(Recommended):
const
ness is only perceived by the user.mutable
in front of variables, like mutable int x;
to allow changeable class members even when const
class instance.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.
/* 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.
static
& Singleton DesignIf 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.