C++ Reference (Part 2)
- Types and Stuff
1.1. decltype and auto
1.2. declval
1.3. typedef and using
1.4. unions
1.5. enums
1.6. volatile keyword
1.7. static keyword
1.8. mutable keyword
1.9. extern keyword
1.10. typename keyword
1.11. storage classes - Raw Pointers
2.1. const keyword
2.2. function pointers - Class Basics
3.1. other ways of instantiating
3.2. member initialization
3.3. static members
3.4. const objects and functions
3.5. template specialization
3.6. template instantiation
3.7. extern templates - Inheritance
4.1. friend functions and classes
4.2. access specifiers
4.3. what's inherited
4.4. base constructor
4.5. virtual functions
4.6. pure virtual and abstract classes
4.7. virtual destructors
4.8. virtual inheritance
4.9. virtual tables and dynamic dispatch
4.10. type erasure - Type Conversion
5.1. through classes
5.2. explicit keyword
5.3. type casting
5.4. typeid
5.5. boolean conversions
5.5. integer conversions
5.7. implicit arithmetic conversions - Exception
6.1.specifcation
6.2.standard exceptions - File IO
7.1. modes
7.2. ascii reading
7.3. ascii writing
7.4. binary reading
7.5. binary writing - Functors
8.1. pros
8.2. runtime templates
8.3. built-in functors
8.4. parameter binding
8.5. regular function to functor
8.6. getting more complex
8.7. functors and sorting
8.8. predicates
8.9. template functors - Iterators
9.1. begin and end
9.2. increment and decrement operators
9.3. index operator
9.4. arrow operator
9.5. dereference operator
9.6. comparison operators - Precompiled Headers
- IEEE 754 and caveats
11.1. special floats
11.2. exponent encoding
11.3. epsilon
11.4. significant digits
11.5. denormalized numbers
11.6. precision
11.7. units in last place
11.8. relative error
11.9. rounding error
11.10. extra bits
11.11. extra algebraic assumption error
11.12. floating point exceptions
11.12. float tricks
auto
deduces the type (obviously) at compile time.
int a = 5;
decltype(a) b; // type of a without initialization
auto c = a; // type of a with initialization
auto
has the same rules for const
-ness, references and pointers as templates:
auto
will never deduce a referenceconst
-ness will only be deduced for references and pointers- value types are always copied (special case for
decltype
)
const int *p = nullptr;
const int i = 0;
auto a1 = p; // const int*
auto *a2 = p; // const int*
auto a3 = *p; // int
auto &a5 = *p; // const int&
auto a5 = i; // int
decltype(auto) a6 = i; // const int
auto also never performs a conversion, making it sometimes better than writing down the type explicitly, although it might perform an unwanted copy. decltype(auto)
can be used for variable declaration, function return type and trailing return type with the purpose of removing the inherent limitations of auto
, namely that it never deduces references and constness (except for explicit const references).
std::declval
converts any type T to a reference type, making it possible to use member functions in decltype expressions without the need to go through constructors.
struct Default { int foo() const { return 1; } };
struct NonDefault
{
NonDefault() = delete;
int foo() const { return 1; }
};
int main()
{
decltype(Default().foo()) n1 = 1; // type of n1 is int
// decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
decltype(std::declval<NonDefault>().foo()) n2 = n1; // type of n2 is int
std::cout << "n1 = " << n1 << '\n'
<< "n2 = " << n2 << '\n';
}
declaring new types :
typdef char C;
using C = char;
using
can be used in combination with classes, temlate types and functions.
// inside classes
template <class T, int SIZE>
class Image
{
using Ptr = std::unique_ptr<Image<T, SIZE>>;
};
// with template
template <int SIZE>
using Imagef = Image<float, SIZE>;
// inside functions
int main()
{
using Image3f = Imagef<3>;
auto image_ptr = Image3f::Ptr(new Image3f);
}
union mix_t
{
int l;
struct {
short hi;
short lo;
}s;
char c[4];
}mix;
union trivia:
- a union always has the size of its largest member
- unions can have constructors and destructors since C++11
- there is no way of knowing whether a union member was constructed
- it is therefor impossible to get the destructor right using information just within the union :) proposal pending
- unions can also have member functions and operator overloading
- a union can have at most one active member at a time. there is no default member
- accessing a non-active member is undefined behavior
- wrapping an object inside a (nameless) union can delay its initialization, e.g.
union UT { std::string s; }
s
will remain inactive until it is constructed, which is "impossibe" the normal way- such objects can be constructed using placement new:
new (&ut.s) std::string();
- or since C++23:
std::construct_at(&ut.s);
//numbered starting from 0
enum colors_t {BLACK, BLUE, GREEN, CYAN, RED, PURPLE, YELLOW, WHITE};
//numbered startin from 1
enum months_t { JANUARY=1, FEBRUARY, MARCH, ...};
enum class or enum struct helps avoid dumb misakes by preventing unwanted int casts.
enum class Colors {BLACK, BLUE, GREEN, CYAN, RED, PURPLE, YELLOW, WHITE};
Colors mycolor;
mycolor = Colors::BLUE;
if (mycolor == Colors::GREEN) mycolor = Colors::RED;
enum class EyeColor : char {blue, green, brown};
usable for masks
enum class EnumType {
OPTION_1 = 10,
OPTION_2 = 0xBE,
...
}
volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. the compiler might sometimes optimize objects that affect program flow e.g. a loop. from the compiler's perspective the object could be changed by a static value but that might not be the case. to avoid such faults, the volatile keyword should be used.
int f(void) {
static int x = 0;
x++;
return x;
}
int main(void)
{
int j;
for (j = 0; j < 5; ++j) {
printf("value of f(): %d\n", f());
}
return 0;
}
returns 0 1 2 3 4
static member functions of a class can be accessed without an object using the scope operator ::
. they can only access static members of the class and don't have access to this
pointer.
static functions outside classes are only declared in the scope of their respective files. they cannot be used outside that file, even when explicitly mentioned in a header file as a prototype.
The mutable storage class specifier is used only on a class data member to make it modifiable even though the member is part of an object declared as const. You cannot use the mutable specifier with names declared as static or const, or reference members.
class A
{
public:
A() : x(4), y(5) {};
mutable int x;
int y;
};
int main()
{
const A var2;
var2.x = 345;
var2.y = 2345; // not allowed
}
used to specify the type and the existance of an object. once all of the source files have been compiled, the linker will resolve all of the references to the one definition that it finds in one of the compiled source files. the definition of the object needs to have “external linkage”, which means that it needs to be declared outside of a function and without the static keyword.
---header.h---
extern int gloabl_x;
void print_gloabl_x();
---source1.cpp---
#include "header.h"
int gloabl_x;
int main()
{
gloabl_x = 5;
print_global_x();
}
---source 2---
#include "header.h"
void print_gloabl_x() {
std::cout << global_x;
}
typename
is also used in contexts where the template resolution might confuse the compiler. For example if we want to create a pointer or use the value type of a templated type, it might be misconstrued as a multiplication wtih a member variable or a member variable on its own. Here we need the typename
keyword to indicate that the identifier that follows is a type.
template <typename T>
class Test {
T::subtype *ptr; // Error: multiplying T::subtype member variable with something called ptr?
typename T::subtype *ptr; // Ok
T::subtype pop(); // Error: is the member variable T::subtype a return type?
typename T::subtype pop(); // Ok
};
the storage class specifiers are a part of the decl-specifier-seq of a name's declaration syntax. together with the scope of the name, they control two independent properties of the name: its storage duration and its linkage.
- auto: automatic storage duration
- register: automatic storage duration. also hints to the compiler to place the object in the processor's register. removed in C++17
- static: static or thread storage duration and internal linkage
- extern: static or thread storage duration and external linkage
- thread_local: thread storage duration
- mutable - does not affect storage duration or linkage
int x;
int * p1 = &x; // non-const pointer to non-const int
const int * p2 = &x; // non-const pointer to const int
int * const p3 = &x; // const pointer to non-const int
const int * const p4 = &x; // const pointer to const int
const int * p2a = &x; // non-const pointer to const int
int const * p2b = &x; // also non-const pointer to const int
CamelCase starting with k
.
int addition (int a, int b) { return a + b; }
int subtraction (int a, int b) { return a - b; }
int call (int x, int y, int (*functocall)(int, int))
{
return (*functocall)(x, y);
}
int main ()
{
int (*minus)(int,int) = subtraction;
int m = call(7, 5, addition);
int n = call(20, m, minus);
}
struct Num
{
int a = 10;
int add(int b) {
return a + b;
}
int subtract(int b) {
return a - b;
}
};
int main()
{
bool perform_add;
std::cin >> perform_add;
Num num_obj;
int (Num::*fptr)(int) = nullptr;
if (perform_add) {
fptr = &Num::add;
} else {
fptr = &Num::subtract;
}
std::cout << (num_obj.*fptr)(20) << std::endl;
// also possible with
std::cout << std::invoke(&Num::add, num_obj, 13) << std::endl;
}
class Circle
{
double radius;
public:
Circle(double r) { radius = r; }
};
Circle foo (10.0); // functional form
Circle bar = 20.0; // assignment init.
Circle baz {30.0}; // uniform init.
Circle qux = {40.0}; // POD-like
and also
Rectangle rectb; // default constructor called
Rectangle rectc(); // function declaration (default constructor NOT called)
Rectangle rectd{}; // default constructor called
in general, list initialization is preferred due to it's reluctance for narrowing. official reference:
list initialization does not allow narrowing (§iso.8.5.4). That is:
- An integer cannot be converted to another integer that cannot hold its value. For example, char to int is allowed, but not int to char.
- A floating-point value cannot be converted to another floating-point type that cannot hold its value. For example, float to double is allowed, but not double to float.
- A floating-point value cannot be converted to an integer type.
- An integer value cannot be converted to a floating-point type
For members of fundamental types, it makes no difference how the constructor is defined, because they are not initialized by default, but for member objects (those whose type is a class), if they are not initialized using member initializer list, they are default-constructed:
class A
{
double r_;
public:
A(double r) : r_(r) {}
};
class B
{
A base;
public:
Cylinder(double r) : base{r} {}
};
or just make a pointer to A like a decent person.
exist exactly once per type, not per object. the value is equal across all instances.
class Dummy
{
static int counter;
Dummy() {counter++;}
};
Dummy::counter = 0;
to avoid them being declared several times, they cannot be initialized directly in the class, but need to be initialized somewhere outside it (always in cpp files, never in headers).
they do not have an object of the class. they can access private members if they are passed to them as parameters. syntax:
ClassName::MethodName(<params>)
const Class object;
- access to its data members from outside the class is restricted to read-only, as if all its data members were const for those accessing them from outside the class. member functions should be const if they are to be called outside the class.
- member functions specified to be const cannot modify non-static data members nor call other non-const member functions. In essence, const members shall not modify the state of an object.
- const objects are limited to access only member functions marked as const, but non-const objects are not restricted and thus can access both const and non-const member functions alike. Most functions taking classes as parameters actually take them by const reference, and thus, these functions can only access their const members, i.e. ensuring that the passed object does not change. overloading constness is also a thing :
class MyClass {
int x;
public:
MyClass(int val) : x(val) {}
const int& get() const {
return x;
}
int& get() {
return x;
}
};
if the class uses templates but for a special data type, it should be declared a bit differently, we declare it twice. once with template classes, and once with an empty template and the specific data type:
// class template:
template <class T>
class mycontainer {
T element;
public:
mycontainer (T arg) {
element = arg;
}
T increase () {
return ++element;
}
};
// class template specialization:
template <>
class mycontainer <char>
{
char element;
public:
mycontainer(char arg) {
element = arg;
}
char uppercase()
{
if (element>= 'a' && element <= 'z') {
element += 'A' - 'a';
}
return element;
}
};
notice that we precede the class name with template<>
with an empty parameter list. this is because all types are known and no template arguments are required for this specialization, but still, it is the specialization of a class template, and thus it requires to be noted as such. when we declare specializations for a template class, we must also define all its members, even those identical to the generic template class, because there is no "inheritance" of members from the generic template to the specialization.
explicit instantiation can be used when the templated type or function is declared inside a header and defined inside a source file. having only access to the header, a separate translation unit cannot instantiate a template at compile time. one must explicitly instantiate the templated type or function where it is defined, so that it can provide the symbols during linkage.
// header.hpp
struct Test
{
template <typename T>
T get_default_value();
};
// template.cpp
template <typename T>
T get_default_value() {
return T{};
}
/* explicit instantiation for int */
template int Test::get_default_value();
// main.cpp
#include "header.hpp"
int main()
{
Test t;
auto fdef = t.get_default_value<float>(); // undefined reference
auto idef = t.get_default_value<int>(); // works
}
for templated functions or types that require a considerable amount of compilation time, we can defer the instantiation to another translation unit by using the extern
keyword. the instantation may be a normal use that would lead to implicit instantation, or an explicit instantiation:
// header.hpp
template <typename T>
T large_function() {
...
}
// file1.cpp
bool some_test() {
...
/* compiles large_function<double> */
return large_function<double>() > 0;
}
// file2.cpp
extern template double large_function<double>();
a non-member function can access the private and protected members of a class if it is declared a friend of that class. that is done by including a declaration of this external function within the class, and preceding it with the keyword friend.
class A
{
private:
int a;
friend void setA(A obj);
friend class B;
};
void setA(A obj) {
A.a=10;
};
class B
{
void changeA(A obj) {
obj.a = 14;
}
};
access | public | protected | private |
---|---|---|---|
members of the same class | y | y | y |
members of derived class | y | y | n |
not members | y | n | n |
- public inheritance: all inherited members will keep their access specifiers in derived class
- protected inheritance : all inherited members will at most be proteted
- private inheritance : all inherited members will be private
- base' constructors and its destructor
- base' assignment operator members (operator=)
- base' friends
- base' private members
base' default constructor is called with every child class object instantiation. a specific base constructor can also be called after colon when declaring child constructors.
class Base
{
public:
Base() {
std::cout << "default base constructor";
}
Base(int a) {
std::cout << a << " base constructor";
}
};
class Derived : public Base
{
public:
Derived(int b) : Base(b) {}
}
if the base only has a non-derfault constructor, the child must have a constructor that calls the base constructor.
are made in the base class to be overridden in the derived class. they can still be called though. if a function is not defined as virtual and still overridden in the child class, a base class pointer that was initialized with the adress of a child object will always call the function in the base class (power of polymorphism goes to shit).
since C++11, it is recommended to add override
after function declaration in the child class. This keyword helps the compiler detect if a virtual function has been overloaded, instead of overridden. for example if in the derived class, we forget the const keyword.
a pure virtual function is a virtual function whose definition is "=0". classes with at least one pure virtual function are called abstract. we cannot create objects of abstract classes but can create pointers for polymorphism. this also doesn't mean that they can't have useful callable functions.
Base *base_ptr = new Child;
class Base
{
public:
Base() { std::cout << "Base Constructor\n"; }
~Base() { std::cout << "Base Destructor\n"; }
};
class Derived : public Base
{
public:
Derived() { memory = new int[500]; std::cout << "Derived Constructor\n"; }
~Derived() { delete memory; std::cout << "Derived Destructor\n"; }
private:
int *memory;
}
int main()
{
Base *base = new Base();
delete base;
std::cout << "---------------\n";
Derived *derived = new Derived();
delete derived;
std::cout << "---------------\n";
Base *poly = new Derived();
delete poly;
}
output:
Base Constructor
Base Destructor
---------------
Base Constructor
Derived Constructor
Derived Destructor
Base Destructor
---------------
Base Constructor
Derived Constructor
Base Destructor
when calling the destructor for poly
, we don't know that the object might have another destructor, since it's cast to Base
pointer. to overcome this issue we have to declare the Base destructor as virtual
. this does not override the base destructor. it just lets C++ know that there might be another destructor further down in the hierrachy.
if there is a diamond structure in the class hierarchy, the bottom class (A
), gets two copies of D
. one through B
and another one through C
.
#include <iostream>
class D {
public:
void foo() {
std::cout << "foo" << std::endl;
}
};
class C: public D {
};
class B: public D {
};
class A: public B, public C {
};
int main(int argc, const char * argv[]) {
A a;
a.foo();
}
to avoid this, B and C should use virtual inheritance.
class C: virtual public D {
};
class B: virtual public D {
};
a routine is created for each normal class method. the compiler remembers its address and dispatches it for all calls to this method. this is known as static dispatch or early binding since the correct address is known at compile time. this obviously fails with polymorphism.
for every class that contains or inherits virtual functions, the compiler constructs a virtual table. the vtable contains an entry for each virtual function accessible by the class and stores a pointer to its definition. entries in the vtable can point to either functions declared in the class itself (overridden), or virtual functions inherited from a base class.
vtables exist at the class level, meaning there exists a single vtable per class, and is shared by all instances. each class instance however has this vpointer added to its beginning. the pointer is 4 bytes or 8 bytes long depending on the cpu architecture.
virtual destrcutors become a clear necessity, since otherwise polymorphic objects would dispatch the base destructor statically.
type erasure is a way of accomplishing the task usually done by polymorphism by hiding the type at runtime. the basic example of type erasure is std::function
which can hold any type that is callable with a certain signature, be it a real function, a lambda, or a bound functor (e.g. bound to an pointer with std::bind_front
. type erasure has the following benefits:
- simpler and faster to compile interfaces
- can work with any feature type that adheres to the interface
- compilation firewall to prevent recompiling the entire library for adding a new type Example:
class animal_view {
public:
template <typename Speakable>
explicit animal_view(const Speakable &speakable)
: object{&speakable},
speak_impl{[](const void* obj) {
return static_cast<const Speakable*>(obj)->speak();
}} {}
void speak() const { speak_impl(object); }
private:
const void* object;
void (*speak_impl)(const void*);
};
void do_animal_things(animal_view animal) { animal.speak() };
int main() {
struct Cow {
void speak() { std::cout << "moo!\n";
};
struct Sheep {
void speak() { std::cout << "hello!\n";
};
do_animal_things(animal_view{Cow{}});
do_animal_things(animal_view{Sheep{}});
}
we did polymorphism without polymorphism or dynamic allocations. we still do a function indriction, similar to dynamic dispatch. that means we gain no performance over virtual functions. all the code does get inlined and the entire call is replaced with a jmp
to the function pointer. the downside of this approach is the risk of dangling pointers.
- If a negative integer value is converted to an unsigned type, the resulting value corresponds to its 2's complement bitwise representation (i.e., -1 becomes the largest value representable by the type, -2 the second largest, ...).
- The conversions from/to bool consider false equivalent to zero (for numeric types) and to null pointer (for pointer types); true is equivalent to all other values and is converted to the equivalent of 1.
- If the conversion is from a floating-point type to an integer type, the value is truncated (the decimal part is removed). If the result lies outside the range of representable values by the type, the conversion causes undefined behavior.
- Otherwise, if the conversion is between numeric types of the same kind (integer-to-integer or floating-to-floating), the conversion is valid, but the value is implementation-specific (and may not be portable).
coverting A ojects to B and vice versa.
class A {};
class B
{
public:
// conversion from A (constructor):
B (const A& x) {}
// conversion from A (assignment):
B& operator=(const A& x) {return *this;}
// conversion to A (type-cast operator)
operator A() {return A();}
};
int main ()
{
A foo;
B bar = foo; // calls constructor
bar = foo; // calls assignment
foo = bar; // calls type-cast operator
return 0;
}
the type-cast operator uses a particular syntax: it uses the operator keyword followed by the destination type and an empty set of parentheses. notice that the return type is the destination type and thus is not specified before the operator keyword.
if we add the function void fn (B arg) {}
to the previous example, it can be called with both foo
and bar
. to avoid such implicit casting, we should define the constructor as explicit : explicit B (const A& x) {}
additionally, constructors marked with explicit cannot be called with the assignment-like syntax; in the above example, bar could not have been constructed with: B bar = foo
.
type-cast member functions (those described in the previous section) can also be specified as explicit. this prevents implicit conversions in the same way as explicit-specified constructors do for the destination type.
there are two main syntaxes for generic type casting:
c like : int b = (int)a;
functional : int b = int(a);
unrestricted explicit type-casting allows to convert any pointer into any other pointer type, independently of the types they point to. the subsequent call to member result will produce either a run-time error or some other unexpected results.
dynamic_cast
can only be used with pointers and references to classes (or with void*). its purpose is to ensure that the result of the type conversion points to a valid complete object of the destination pointer type. this naturally includes pointer upcast (converting from pointer-to-derived to pointer-to-base), in the same way as allowed as an implicit conversion. but dynamic_cast
can also downcast (convert from pointer-to-base to pointer-to-derived) polymorphic classes (those with virtual members) if -and only if- the pointed object is a valid complete object of the target type.
class Base { virtual void dummy() {} };
class Derived: public Base { int a; };
int main ()
{
Base *pba = new Derived;
Base *pbb = new Base;
Derived *pd;
pd = dynamic_cast<Derived*>(pba); // works
pd = dynamic_cast<Derived*>(pbb); // produces nullptr
}
even though both are pointers of type Base*, pba actually points to an object of type Derived, while pbb points to an object of type Base. therefore, when their respective type-casts are performed using dynamic_cast
, pba is pointing to a full object of class Derived, whereas pbb is pointing to an object of class Base, which is an incomplete object of class Derived.
if dynamic_cast
is used to convert to a reference type and the conversion is not possible, an exception of type bad_cast is thrown instead. dynamic_cast
can also perform the other implicit casts allowed on pointers: casting null pointers between pointers types (even between unrelated classes), and casting any pointer of any type to a void*
.
Google style suggests avoiding dynamic casts.
static_cast
can perform conversions between pointers to related classes, not only upcasts (from pointer-to-derived to pointer-to-base), but also downcasts (from pointer-to-base to pointer-to-derived). no checks are performed during runtime to guarantee that the object being converted is in fact a full object of the destination type. therefore, it is up to the programmer to ensure that the conversion is safe. on the other side, it does not incur the overhead of the type-safety checks of dynamic_cast
.
class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast<Derived*>(a);
is a valid code but could lead to runtime errors if dereferenced.
reinterpret_cast converts any pointer type to any other pointer type, even of unrelated classes. the operation result is a simple binary copy of the value from one pointer to the other. All pointer conversions are allowed: neither the content pointed nor the pointer type itself is checked.
it can also cast pointers to or from integer types. the format in which this integer value represents a pointer is platform-specific. the only guarantee is that a pointer cast to an integer type large enough to fully contain it (such as intptr_t), is guaranteed to be able to be cast back to a valid pointer.
class A { /* ... */ };
class B { /* ... */ };
A * a = new A;
B * b = reinterpret_cast<B*>(a);
this code compiles, although it does not make much sense, since now b
points to an object of a totally unrelated and likely incompatible class. dereferencing b
is unsafe.
this type of casting manipulates the constness of the object pointed by a pointer, either to be set or to be removed. for example, in order to pass a const pointer to a function that expects a non-const argument.
void print (char * str) {
std::cout << str << '\n';
}
int main ()
{
const char *c = "sample text";
print(const_cast<char *>(c));
return 0;
}
the example above is guaranteed to work because function print
does not write to the pointed object. note though, that removing the constness of a pointed object to actually write to it causes undefined behavior.
typeid allows to check the type of an expression: typeid (expression)
this operator returns a reference to a constant object of type type_info that is defined in the standard header <typeinfo>
. a value returned by typeid can be compared with another value returned by typeid using operators == and != or can serve to obtain a null-terminated character sequence representing the data type or class name by using its name() member.
#include <typeinfo>
int main () {
int *a,b;
a = 0; b = 0;
if (typeid(a) != typeid(b)) {
cout << "a and b are of different types:\n";
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
}
return 0;
}
when typeid is applied to classes, typeid uses the RTTI to keep track of the type of dynamic objects. when typeid is applied to an expression whose type is a polymorphic class, the result is the type of the most derived complete object.
class Base { virtual void f(){} };
class Derived : public Base {};
int main () {
Base* a = new Base;
Base* b = new Derived;
cout << "Base is: " << typeid(&Base).name() << '\n'; // Base
cout << "Derived is: " << typeid(&Derived).name() << '\n'; // Derived
cout << "a is: " << typeid(a).name() << '\n'; // Base*
cout << "b is: " << typeid(b).name() << '\n'; // Base*
cout << "*a is: " << typeid(*a).name() << '\n'; // Base
cout << "*b is: " << typeid(*b).name() << '\n'; // Derived
}
the string returned by member name of type_info
depends on the specific implementation of your compiler and library. it is not necessarily a simple string with its typical type name, like in the compiler used to produce this output. if the type typeid
evaluates is a pointer preceded by the dereference operator (*
), and this pointer has a null value, typeid throws a bad_typeid exception.
Depending on the object, typeid might return mangled names. GCC offers a demangle functionality. It can receive and populate a buffer but it can't resize it so there's basically no point to passing a pointer to length.
#include <cxxabi.h>
char* abi::__cxa_demangle(const char* mangled_name, char* output_buffer, size_t* length, int* status);
Example (creates a memory leak):
#include <typeinfo>
#include <cxxabi.h>
#include <vector>
#include <string>
struct ExStruct {
std::vector<std::vector<std::string>> data;
};
int main () {
int status;
cout << "Base is: " << abi::__cxa_demangle(typeid(&ExStruct::data).name(), nullptr, 0, &status) << '\n';
return 0;
}
a value of any scalar type can be implicitly converted to bool
. The values that compare equal to zero are converted to 0
, all other values are converted to 1
.
bool b1 = 0.5; // b1 == 1 (0.5 converted to int would be zero)
bool b2 = 2.0*_Imaginary_I; // b2 == 1 (but converted to int would be zero)
bool b3 = 0.0 + 3.0*I; // b3 == 1 (but converted to int would be zero)
bool b4 = 0.0/0.0; // b4 == 1 (NaN does not compare equal to zero)
a value of any integer type can be implicitly converted to any other integer type. except where covered by promotions and boolean conversions above, the rules are
- if the target type can represent the value, the value is unchanged
- otherwise, if the target type is unsigned, the value
2^b
, where b is the number of bits in the target type, is repeatedly subtracted or added to the source value until the result fits in the target type. in other words, unsigned integers implement modulo arithmetic.- otherwise, if the target type is signed, the behavior is implementation-defined (which may include raising a signal)
char x = 'a'; // int -> char, result unchanged
unsigned char n = -123456; // target is unsigned, result is 192 (that is, -123456+483*256)
signed char m = 123456; // target is signed, result is implementation-defined
assert(sizeof(int) > -1); // assert fails:
// operator > requests conversion of -1 to size_t,
// target is unsigned, result is SIZE_MAX
the arguments of the following arithmetic operators undergo implicit conversions for the purpose of obtaining the common real type, which is the type in which the calculation is performed:
- binary arithmetic
*
,/
,%
,+
,-
- relational operators
<
,>
,<=
,>=
,==
,!=
- binary bitwise arithmetic
&
,^
,|
- the conditional operator
?:
if one of the operands has the base type long double, double or float, the other operand is also converted to this base type. e.g. int > float
-> float > float
and complex > double
-> double complex > double
.
integral types are never implicitly converted to lower integral types in a narrowing conversion. they may however be promoted to to a common type.
if the types are the same:
that type is the common type.
else:
if the types have the same signedness (both signed or both unsigned):
the operand whose type has the lesser conversion rank is implicitly converted to the other type.
else:
if the unsigned type has conversion rank greater than or equal to the rank of the signed type:
the operand with the signed type is implicitly converted to the unsigned type.
else:
// the unsigned type has conversion rank less than the signed type
if the signed type can represent all values of the unsigned type:
the operand with the unsigned type is implicitly converted to the signed type.
else:
both operands undergo implicit conversion to the unsigned type counterpart of the signed operand's type.
integer promotion is the implicit conversion of a value of any integer type with rank less or equal to rank of int
or of a bit field of type bool
, int
, signed int
, unsigned int
, to the value of type int
or unsigned int
.
if int
can represent the entire range of values of the original type (or the range of values of the original bit field), the value is converted to type int
. otherwise the value is converted to unsigned int
.
rank is a property of every integer type and is defined as follows:
- the ranks of all signed integer types are different and increase with their precision: rank of
signed char
< rank ofshort
< rank ofint
< rank oflong int
< rank oflong long int
- the ranks of all signed integer types equal the ranks of the corresponding unsigned integer types
- the rank of any standard integer type is greater than the rank of any extended integer type of the same size (that is, rank of
__int64
< rank oflong long int
, but rank oflong long
< rank of__int128
due to the rule (1)) - rank of
char
equals rank ofsigned char
and rank ofunsigned char
- the rank of
bool
is less than the rank of any other standard integer type - the rank of any enumerated type equals the rank of its compatible integer type
- ranking is transitive: if rank of T1 < rank of T2 and rank of T2 < rank of T3 then rank of T1 < rank of T3
- any aspects of relative ranking of extended integer types not covered above are implementation defined
Note: integer promotions are applied only
- as part of usual arithmetic conversions (see above)
- as part of default argument promotions
- to the operand of the unary arithmetic operators + and -
- to the operand of the unary bitwise operator ~
- to both operands of the shift operators << and >>
thrown using throw
keyword :
int main ()
{
try {
throw 20;
} catch (int e) {
std::cout << "an exception occurred. Exception Nr. " << e << '\n';
}
return 0;
}
if an ellipsis (...)
is used as the parameter of catch, that handler will catch any exception no matter what the type of the exception thrown.
older code may contain dynamic exception specifications. they are now deprecated in C++, but still supported. a dynamic exception specification follows the declaration of a function, appending a throw specifier to it. for example:
double myfunction (char param) throw (int);
this declares a function called myfunction, which takes one argument of type char and returns a value of type double. if this function throws an exception of some type other than int, the function calls std::unexpected
instead of looking for a handler or calling std::terminate
. if this throw specifier is left empty with no type, this means that std::unexpected
is called for any exception. functions with no throw specifier (regular functions) never call std::unexpected
, but follow the normal path of looking for their exception handler.
the C++ Standard library provides a base class specifically designed to declare objects to be thrown as exceptions. it is called std::exception
and is defined in the <exception>
header. this class has a virtual member function called what that returns a null-terminated character sequence (of type char*
) and that can be overridden in derived classes to contain some sort of description of the exception.
#include <exception>
#include <iostream>
class myexception: public std::exception
{
virtual const char* what() const throw() {
return "My exception happened";
}
} myex;
int main ()
{
try {
throw myex;
} catch (std::exception& e) {
std::cout << e.what() << '\n';
}
return 0;
}
standard exceptions are as follows :
exception | description |
---|---|
bad_alloc | thrown by new on allocation failure |
bad_cast | thrown by dynamic_cast when it fails in a dynamic cast |
bad_exception | thrown by certain dynamic exception specifiers |
bad_typeid | thrown by typeid |
bad_function_call | thrown by empty function objects |
bad_weak_ptr | thrown by shared_ptr when passed a bad weak_ptr |
logic_error | error related to the internal logic of the program |
runtime_error | error detected during runtime |
logic_error
and runtime_error
are generic exceptions that can be inherited by custom exceptions to report errors.
Google Style : don't use exceptions
#include <fstream>
using Mode = std::ios_base::openmode;
// input stream
std::ifstream f_in(std::string& filename, Mode mode);
// output stream
std::ofstream f_in(std::string& filename, Mode mode);
// IO stream
std::fstream f_in(std::string& filename, Mode mode);
mode | effect |
---|---|
ios_base::app |
append output |
ios_base ::ate |
seek to EOF when opened |
ios_base ::binary |
open in binary mode |
ios_base ::in |
open for reading |
ios_base ::out |
open for writing |
ios_base ::trunc |
overwrite existing file |
ifstream in ("file.txt", ios_base::in);
int a,b;
std::string str;
double d;
while (in >> a >> str >> b >> d) {
...
}
ifstream in ("file.txt", ios_base::in);
std::string line;
int value;
while (getline(in, line)) {
string::size_type loc = line.find("value", 0);
if (loc != string::npos)
value = line.substr(line.find("=",0) + 1, string::npos);
}
#include <iomanip>
#include <fstream>
ofstream outfile("file.txt");
if(!outfile.isopen())
return EXIT_FAILURE;
outfile << "stuff" << std::endl;
must know the structure beforehand, syntax :
file.read(reinterpret_cast<char*> (&a), sizeof(a));
#include <fstream>
#include <vector>
ifstream file("image.dat", ios_base::in | ios_base::binary);
if (!file) return EXIT_FAILURE;
int r = 0, c = 0;
file.read(reinterpret_cast<char*>(&r), sizeof(r));
file.read(reinterpret_cast<char*>(&c), sizeof(c));
std::cout<<"dimensions " << r << "x" << c;
vector<float> vec(r * c, 0);
file.read(reinterpret_cast<char*>(&vec.front()), vec.size()*sizeof(vec.front()));
writing a sequence of byte, no precision loss for float types, fast. syntax:
file.write(reinterpret_cast<char*> (&a), sizeof(a));
#include <fstream>
#include <vector>
ofstream file("image.dat", ios_base::out | ios_base::binary);
if (!file) return EXIT_FAILURE;
int r = 2, c = 3;
vector<float> vec(r*c, 42);
file.write(reinterpret_cast<char*>(&r), sizeof(r));
file.write(reinterpret_cast<char*>(&c), sizeof(c));
file.write(reinterpret_cast<char*>(&vec.front()), vec.size()*sizeof(vec.front()));
function objects
struct X
{
public:
void operator()(string str){
std::cout << calling functor X with parameter << str << std::endl;
}
};
int main()
{
X foo;
foo("Hi");
return 0;
}
this is not to be mistaken with a type conversion function where the type comes after the operator
keyword
class X
{
...
operator string() const { return "X"; }
};
- normal functions are only called with the
()
operator but functors can also remember states. - functors can have their own datatypes on top of their function signature
with templates we can customize how certain functions work, however their template has to be intialized at compile time. with functors we can do more
struct AddValue
{
int val_;
AddValue(int j): val_(j) {}
void operator()(int i){
std::cout << i + val << std::endl;
}
};
int main()
{
std::vector<int> vec = {1, 5, 2, 3};
int x;
std::cin >> x;
std::for_each(vec.begin(), vec.end(), AddValue(x));
}
std has implemented some functors:
less greater greater_equal less_equal not_equal_to
logical_and logical_not logical_or
multiplies minus plus divide modulus negate
//example
int x = std::multiplies<int>()(3, 4);
if (not_equal_to<int>()(x, 10)) {
std::cout << x << std::endl;
}
std::set<int> myset = {2, 3, 4, 5};
std::vector<int> vec;
// code to multiply each element of the set
// by 10 and save in vector
std::transform(myset.begin(), myset.end(), // source
std::back_inserter(vec), // destination
std::bind(multiplies<int>(), std::placeholders::_1, 10));
transform takes a function that accepts one arguement, however multiplies<int>()
accepts two. we use std::bind
to replace the first parameter with a member of myset
using std::placeholders::_1
.
we can also usestd::bind
in the adding example. e.g. for adding with 2
void AddVal(int i, int val){
std::cout << i + val << std::endl;
}
std::for_each(vec.begin(), vec.end(), bind(AddVal, std::placeholders::_1, 2);
double Pow(double x, double y) {
return pow(x,y);
}
int main()
{
std::set<int> myset = {3, 1, 25, 7, 12};
std::dequeue<int> d;
auto func = std::function<double(double, double)>(Pow);
std::transform(myset.begin(), myset.end(),
std::back_inserter(d),
std::bind(func, std::placeholders::_1, 2));
return 0;
}
bool ElgibileForCopy(int x) {
return (x > 20) || (x < 5);
}
int main()
{
std::set<int> myset = {3, 1, 25, 7, 12};
std::dequeue<int> d; // x in myset : x<20 || x<5
auto func = std::function<double(double, double)>(Pow);
std::transform(myset.begin(), myset.end(),
std::back_inserter(d),
std::bind(std::logical_or<bool>(
std::bind(greater<int>(), std::placeholders::_1, 20),
std::bind(less<int>(), std::placeholders::_1, 5)));
// same as
std::transform(myset.begin(), myset.end(),
std::back_inserter(d),
ElgibileForCopy); // probably won't work. it's a function not a functor
return 0;
// same as
std::transform(myset.begin(), myset.end(),
std::back_inserter(d),
[] (int x) { return (x>20)||(x<5);});
}
we can choose how our containers are sorted.
std::set<int> myset = {3, 1, 25, 7, 12}; // myset : {1, 3, 7, 12, 25}
std::set<int, std::less<int>> myset = {3, 1, 25, 7, 12}; // myset : {1, 3, 7, 12, 25}
struct Lsb_less
{
bool operator ()(int x, int y) {
return (x % 10) < (y % 10);
}
};
int main() {
std::set<int, Lsb_less> myset = {3, 1, 25, 7, 12}; // myset : {1, 12, 3, 25, 7}
}
a predicate is a functor that
- returns a boolean
- does not modify data
predicates are used for comparisons or condition checks.
struct NeedCopy
{
bool operator()(int x) {
return (x>20)||(x<5);
}
};
std::transform(myset.begin(), myset.end(), std::back_inserter(d), NeedCopy());
to reuse defined functors to some extent, we can make use of templates
template <typename T>
struct MatchFirstFunctor
{
MatchFirstFunctor(T const& t) : m_t(t) {}
template<typename U>
bool operator()(U const& pair) {
return pari.first == m_t;
}
T m_t;
}
int main() {
std::vector<std::pair<int, bool>> v1 = {std::make_pair(1, true), std::make_pair(2, false)};
std::vector<std::pair<bool, floa>> v2 = {std::make_pair(true, 1.0f), std::make_pair(false, 1.2f)};
std::find(v1.begin(), v1.end(), std::make_pair(1, true));
std::find_if(v1.begin(), v1.end(), MatchFirstFunctor<int>(2));
std::find_if(v2.begin(), v2.end(), MatchFirstFunctor<bool>(false));
return 0;
}
used to access the elements of collections. could be indexing a contiguous array or iterating a graph. types that have iteratos can be used in range-based for loops. Gemeral structure is as follows:
template <typename Col>
class CollectionIterator {
public:
using value_type = typename Col::value_type;
using pointer_type = value_type*;
using reference_type = value_type&;
CollectionIterator(pointer_type ptr) : ptr_(ptr) {}
private:
pointer_type ptr_;
};
template <typename T>
class Collection {
using value_type = T;
using iterator_type = CollectionIterator<Collection<T>>;
...
};
the Collection class itself must implement the begin()
and end()
functions.
iterator_type begin() {
return iterator_type(data_);
}
iterator_type end() {
return iterator_type(data_ + size_);
}
for stepping over elements, the post/pre increment/decrement operators should be overloaded in the iterator class.
CollectionIterator& operator++() {
ptr_ += 1;
return *this;
}
CollectionIterator operator++(int) {
auto it = *this;
ptr_ += 1;
return it;
}
CollectionIterator& operator--() {
ptr_ -= 1;
return *this;
}
CollectionIterator operator--(int) {
auto it = *this;
ptr_ -= 1;
return it;
}
the iterator class should also implement the index operator for random access:
referemce_type operator[](size_t index) {
return *(ptr_ + index);
}
the arrow operator should be overloaded in the iterator class to allow for member access. this operator has no inputs. it can techinically return anything, but should return something that either is a pointer or can become a pointer through chained ->
operators. the ->
operator automatically dereferences its return value before calling its argument using the built-in pointer dereference, not operator*
.
pointer_type operator->() {
return ptr_;
}
also should be overloaded by the iterator class:
reference_type operator*() {
return *ptr_;
}
required in the iterator class to terminate iteration.
bool operator==(const CollectionIterator& other) const {
return ptr_ == other.ptr_;
}
bool operator!=(const CollectionIterator& other) const {
return ptr_ != other.ptr_;
}
when we include a header file e.g. vector
, we're potentially adding thousands of lines to be compiled each time we change anything in the project. this significantly increases compilation time. a precompiled header is already turned into a binary format that can easily be integrated into the chain.
precompiled headers are mostly used for external code. for example we won't ever change STL headers or windows.h
a bad practice is to add all the dependencies to a PCH. this hides what is being used in each cpp file.
putting frequently changing headers into a PCH will only increase compile time.
//pch.h
#pragma once
#include <algorithm>
#include <vector>
#include <deque>
#include <stack>
#include <array>
#include <set>
#include <map>
#include <string>
#include <thread>
#include <memory>
#include <Windows.h>
we first compile the header, then the file that's using it.
g++ -std=c++11 pch.h
g++ -std=c++11 main.cpp
not supported by gcc. workarounds are bad.
a fixed-point binary format has a static position for the decimal point. the bits to its left represent the whole part while the right bits represent the fraction. fixed-point representation can vastly simplify the computation of opeartions but also limits range and precision.
a floating-point uses the scientific notation. they are represented by the IEEE 754 binary format on almost all modern architectures.
x = -1^(sign bit) x 2^(exponent - 127) x 1.mantissa
- 32-bit: 1 sign bit + 8 exponent bits + 23 mantissa bits
- 64-bit: 1 sign bit + 12 exponent bits + 52 mantissa bits
- divide by zero:
1 / 0
- not a number (NaN):
0 / 0
,0 x ∞
- signed infinity: for overflow protection
- signed zero: underflow protection for very small numbers. preserves sign.
+0 == -0
auto zeroPointOne = 0.1f;
auto zeroPointTwo = 0.2f;
auto zeroPointThree = 0.3f;
auto sum = zeroPointOne + zeroPointTwo;
std::cout << zeroPointOne << "\n"; // 0.100000000149...
std::cout << zeroPointTwo << "\n"; // 0.200000000298...
std::cout << zeroPointThree << "\n"; // 0.300000001192...
std::cout << sum << "\n"; // 0.300000001192...
how about using double instead?
std::cout << zeroPointOne << "\n"; // 0.100000000000000005551115123126
std::cout << zeroPointTwo << "\n"; // 0.200000000000000011102230246252
std::cout << zeroPointThree << "\n"; // 0.299999999999999988897769753748
std::cout << sum << "\n"; // 0.300000000000000044408920985006
1.0 = -1^0 x 2^(0) x 1.0
the exponent itself is an 8-bit unsigned integer. 2^(exponent - 127)
can therefor be between 2^-126
to 2^+127
. here exponent - 127
must be zero, therefor the floating point representation will be: [0][01111111][00000000000000000000000]
epsilon is the difference between 1.0
and smallest number that is larger than 1.0
. using the last example, it is [0][01111111][00000000000000000000001] = 1.0000001192092896
. it is accessible using std::numeric_limits<T>::epsilon
for floating-point type T
.
they measure precision. in order to compare two floating-point numbers solely based on the mantissa, they would have to have the same exponent. otherwise, the number of signficant digits changes based on the exponent. even though we will have a lot of decimal places, there is a measure of their accuracy and the rest is noise.
size | sign | exponent | mantissa | total | exponent bias | bits precision | significant digits |
---|---|---|---|---|---|---|---|
half | 1 | 5 | 10 | 16 | 15 | 11 | 3-4 |
single | 1 | 8 | 23 | 32 | 127 | 24 | 6-9 |
double | 1 | 11 | 52 | 64 | 1023 | 53 | 15-17 |
x86 extended precision | 1 | 15 | 64 | 80 | 16383 | 64 | 18-21 |
Quad | 1 | 15 | 112 | 128 | 16383 | 113 | 33-36 |
for 32-bit floats, the minimum base 10 exponent is -36. how an we represent smaller numbers?
since the exponent field is 127 left shift-encoded, we get the smallest value by setting all of its bits to zero (2^(0 - 127) = 2^-127
).
we can represent 1e-37 using denormalized numbers, i.e. numbers where the exponent bits are zero. this helps prevent underflow. example:
[0][00000000][11011001110001111101110] = 0.09999999e-37
as a result of their implementation, the representation is not uniform between numbers. we have the most precision between 0.0
and 0.1
(applies to negative values too). after this, the precision starts to diminish. std::nextafter
allows us to get the very next possible number after the given one.
for 32-bit floats, the representable number distribution is as follows:
from | to | individual numbers |
---|---|---|
0.0 | 0.1 | 1'036'831949 |
0.1 | 0.2 | 8'388'608 |
0.2 | 0.4 | 8'388'608 |
0.4 | 0.8 | 8'388'608 |
0.8 | 1.6 | 8'388'608 |
1.6 | 3.2 | 8'388'608 |
pi represented as a floating point number has some errors:
pi = 3.141589265
pif = 3.141589274
d = 0.000000009
ulps is defined as the gap between two floating-point numbers nearest to x, even if x is one of them. for pi, we get a ulps of 9. IEEE 754 requires that elementary arithmetic operations are correctly rounded to within 0.5 ulps. transcendal functions are generally rounded to 0.5-1.0 ulps.
the difference between the real number and the approximated representation, devided by the real number.
relative error = (pi - pif) / pi = 2.864789e-8
induced by approximating an infinite range of numbers into a finite number of bits. math is done exactly, then rounded:
- towards the nearest representable number
- towards zero
- towards positive infinity (round up)
- towards negative infinity (round down)
if there is a tie between rounding up & down, the number is rouneded to the one with the even mantissa. this avoids the bias of always rounding up or down.
during calculations, 3 extra bits are considered per floating point number, although they are not saved in the float itself:
- gaurd bit
- rounding bit
- sticky bit
gaurd bit and rounding bit are extra precision. the sticky bit is an OR of anything that passes through it.
[G][R][S] | action |
---|---|
[0][-][-] | round down (do nothing) |
[1][0][0] | round up if mantissa lsb is 1 |
[1][0][1] | round up |
[1][1][0] | round up |
[1][1][1] | round up |
mathematical identites (associative, commutative, distributive) do not hold for floating point numbers.
- distributive:
x * y - x * z != x * (y - z)
- associative:
x + (y + z) != (x + y) + z
- cannot interchange division and multiplication:
x / 10.0 != x * 0.1
IEEE exception | result when traps disabled | argument to trap handler |
---|---|---|
overflow | +/- ∞ or +/- xmax | round(x2^-alpha) |
underflow | 0, 2^e_min or denormalized | round(x2^alpha) |
divide by zero | +/- ∞ | invalid operation |
invalid | NaN | invalid operation |
inexact | round(x) | round(x) |
where alpha is 192 for single precision, 1536 for double.
there are some tricks for the IEEE specification. a famous one being quake's fast reciprocal square root approximation of numbers larger than 0.25:
inline float fast_inv_sqrt(float x) {
int tmp = ((0x3f800000 << 1) + 0x3f800000 - *(long*)&x) >> 1;
auto y = *(float*)&tmp;
return y * (1.47f - 0.47f * x * y * y);
}