C++ : Part I

c++: structure defaults to public, class defaults to private. 
Struct can also have constructors, destructor.
Unions can also have constructor, destructor, member function, access control.

Union vs Class: The way data members are handled. Also, union can not be used as base class for inheritance. 

#include <iostream>
using namespace std;
union U{private: int i; float f;
        public: U(int a); U(float a); ~U(); int readi(); float readf();};
U::U(int a){ i=a;}
U::U(float a){ f=a;}
U::~U(){}
int U::readi(){return i;}
float U::readf(){return f;}
int main(){U I(10); U F(10.0F); cout<<I.readi(); cout<<F.readf();}
//Here no one stops us from calling I.read_float();

Also interesting fact, if you say, U F(10.0), you get compiler error:
 error: call of overloaded 'U(double)' is ambiguous

Untagged Enumeration: When enum has no type name but has variable name. It is acceptable as long as instance of enum is defined immediately and there is no need to refer it later.
enum { char, int, float, double} var_type;

Anonymous union: no type name and no variable name
union { char c;  int i;  float f;  };
The members can be accessed directly. note: c, i, f occupy same space.

Anonymous union at file scope must be declared static to make internal linkage.

Access Control
public: 
private: (default) No one except creator of type can access. compile error.
protected: 
public: accessed with (.) operator, not true for private, protected.

Friends: To define who can access private implementation
Who can be friend: A global function, a member function of another class, even entire class.

Friend functions: Not actually class functions. Gives private access to non-class functions.
A global function, or member function of another class can be declared as friend in a class.
class wFriend{ int i; public: friend void fun(); //Global fun() };

class other{ void fun(); };
class wFriend{ private: int i;  public: friend void other::fun();
  friend class Other; //Make fulll class friend
};

Because of friend concept, C++ is not called pure OOP, because it violates concept of encapsulation.



class B{ int i; 
   public: B(); ~B(); 
};

B b; //At this point, compiler inserts B::B() with first argument this pointer having address of object for which it is called. For constructor, this points to uninitialized memory, and constructor to init it.

constructor and destructor have no return type, as they are default invoked by compiler who has no business to handle their return values.
A default (no arg) is automatically created ONLY if (and only if) we don't define any constructor.
If need to define constructor with arg, need to define default as well, if wish to use without arg constructor as well.

destructor called by compiler when object goes out of scope. Not as evident as constructor call, but around the time closing } of scope of object.


Aggregate Initialization
int a[5]={1,2,3,4,5];
//If we give more than 5 initializers, compile error
//If we give less than 5 initializers, rest initialized to 0.
int arr[]={1,2,3,4,5,6};
How to find its size? sizeof(arr)/sizeof(*arr)

class B{ int i, j; public:B(int aa, int bb); };
B::B(int aa, int bb){ i=aa; j=bb;}

B b[] = {B(1,2), B(3,4), B(5,6)};   //sizeof(b)/sizeof(*b);

Function Overloading: Same function name can be used as long as argument lists are different and return type is same. Scope rules apply, i.e. same function can be global as well as within class. It is possible due to compiler's name decoration feature(explained below).

Default Arguments
C(int size, float f=10.0F); 
only trailing arguments may be defaulted, once default arguments start, remaining argument must be defaulted.
Only placed in declaration of function. 

Overloading vs Default Arg: 
int func(int a, int b=2); is same as:
int func(int a);  or   int func(int a, int b);
A use of default argument is to maintain backward compatibility, i.e. when you wish to add new arguments to function without bothering old users.

Placeholder Arguments
Function arguments can be declared without identifiers(names). 
void f(int x, int=0, float=10.0F)//can only refer x in function body.
Benefit? If define argument without using in function body, it generates compiler warning. To avoid it, until the usage for this argument becomes clear.

Name Decoration
Compiler decorates name, scope and argument list to distinguish different functions.

file.cpp
#include <iostream>
using namespace std;
int func(){cout<<"Hello"; }
int func(int X){ cout<<"Hello"; }
int func(int X, float Y) { cout<<"Hello"; }
class V {
        int i; // private
        public:
        int func(){cout<<"Hello";}
        int func(int x){cout<<"Hello";};
        int func(int x, float y){cout<<"Hello";};
};
int main()
{
        V v;
        func();  func(10);  func(10,20);
        v.func();  v.func(100);  v.func(100, 200);
        return 0;
}

How does compiler distinguish? Same func() is defined global with different parameters.
Using Name decoration, something like func, func_int, func_int_float, X_func, X_func_int, X_func_int_float.

To read what compiler does
g++ --save-temps file.cpp
vi file.s

In reality, the names in assembly file (.s) are not so straightforward (g++) and rather look encrypted. But concept looks same. But you can make out:
    funcv, funci, funcif, VfunEc, VfuncEi, VfuncEif [V-class, i-int, f-float, v-void, E??].

Why can't name decoration be extended to return type?
A function (returning valid type) can be called without bothering about return type. In such case, how does compiler know which function is called?

Benefit of Decoration: Type-safe linkage
file.cpp: void f(int) {}
file2.cpp: void f(char); int main(){ //f(1); return 0; }
Since compiler decorates, f_char, and when passed int, it throws linker error. This would have passed linker too in C.


Constant const
Motivation: Eliminate #define, but gradually extended to pointers, function arg, return type, class object, member functions etc. Also used to safeguard consts, like const int etc.
Also, const has scope rules, not true for #define.

Always assign value to const when define it, except when making explicit declaration using extern const int size;

Storage Allocation for const: In usual case, C++ compiler keeps const in its symbol table and does not allocate storage.
But when used with extern, storage is allocated by compiler to facilitate several translation units to refer to this item. Other case where storage is allocated is when address of const is used.

const int i = 100; //usual compile time const
const int j = i + 20; //value from const expression
long address = (long)&j; //forces storage for j
char buf[j + 10]; //const expression, even though storage is allocated for j
const char c = cin.get(); //value not known at compile time, storage required
const char c2 = c + 'a'; //

const for Aggregates: storage will be allocated as compiler can not keep aggregate in symbol table.
const int i[]={1,2,3,4,5};   float f[i[2]] //Illegal
const B b[]={{1,2},{3,4}};  int x[b[0].j]; //Illegal
For arrays, compiler need to generate code to move stack pointer to accommodate array. As it can not find constant expression(i[2] or b[0].j), it complains.

const in C++ defaults to internal linkage, it is illegal to declare const in file1 and use extern in file 2. To do so, const need to be defined to give external linkage as below:
extern const int i=10; //init and say extern. init makes it definition, not just declaration.

In C: const always get storage, treated global.
const int i=10;  int buf[i];//illegal as C compiler does not know value of i at compile time.

const Pointers: const specifier binds to closest thing.
const int d=10; //d is const int
const int *i; //i is pointer, pointing to const int
int const *i; //is is const pointer to int.
int *const i=&var; //const i in pointer to int.

Neigher pointer, nor value can be changed:
int d=10;
const int* const p1=&d;//const p1 is pointer to const int
int const* const p2=&d;//const p2 is pointer to int const

Coding suggestion: the const is always placed to the right of what it modifies.

Address of const obj can not be given to non-const ptr. You can cast, but bad practice.
Address of non-const obj can be given to const ptr.

int d=10;  const int f=2;
int *i=&d; //OK     int *v=&fe;//Not OK as v is pointer to integer, not const int
int *w=(int*)&f); //legal, bad practice

It makes little sense to pass or return const by value. For pass by referene it is more meaningful.Pass by const value
void func(const x){ x++; //Illegal}
//Not sure why, coz when passed by value, its local copy is created inside func.
clean looking implementation:
void f2(int ic) { const int& i = ic;  i++; // Illegal -- compile-time error  }

Return by const value
Return by const value does not make much sense for built in types
int f3() { return 1; }

const int f4() { return 1; }
const int j = f3(); // Works ok

int k = f4(); // But this works ok too!

But it is important for user defined types. When function returns class object by value, the return value can not be lvalue(can not be assigned to or modified otherwise)

Only the non-const return value can be used as an lvalue.



Temporary Objects: created by compiler and destroyed. require storage like other objects. They are automatically const. 


class B{int i; public: X(int a=0){i=a;}  void modify();};
B::modify(){i+=10;};

B f1(){return B();}
const B f2(){return B();}
void f3(B& b) { b.modify();} //takes arg as non-const reference, same as taking non-const ptr with a different syntax

f1()=B(1); //OK
f1().modify(); //OK

Below are compile error:
f3(f1());   f2()=B(1);   f2().modify();  f3(f2());

e.g. f3(f1()): f1() returns non-const, compiler must create temp var to hold f1() returned object to pass to f2(). As long as f7() took argument by value, it was fine. But it takes argument by reference, and there is no reference possible for temp variables.

f1()=B(1);  and f1().modify(): Here also compiler need to create temp object to hold f1() return value. 

Pass and Return Address
When we return address (ptr or ref), client code can modify this and use differently. When we are returning address, it is recommended to make it const. 

A function that takes a const pointer is more general than one that does not.

void t(int*) {}  //will not accept ptr to const.
int x = 0;
int* ip = &x;
const int* cip = &x;
t(ip); // OK
//! t(cip); // Not OK

void u(const int* cip) { //u() will accept both const, non-const ptr
//! *cip = 2; // Illegal -- modifies value
int i = *cip; // OK -- copies value
//! int* ip2 = cip; // Illegal: non-const
}
u(ip); // OK
u(cip); // Also OK

// Returns address of static character array, Its return can be assigned only to ptr to const:
const char* v() {   return "result of function v()";   }
//! char* cp = v(); // Not OK
const char* ccp = v(); // OK


const int* const w() {   static int i;   return &i;  }//i is static storage area, so valid after return
# second cont is applicable only when w() is used as lvalue and give error if tried as const int *
//! *w() = 1; // Not OK
# Otherwise its return value can be assigned to both const int *cont, and const int *
const int* const ccip = w(); // OK
const int* cip2 = w(); // OK
//! int* ip2 = w(); // Not OK

Guidelines: How should pointer be returned in C++?
In C: when passing by reference use pointer
In C++: pass by reference and use const reference

Since reference syntax looks like pass by value, temp objects can be passed; which is not possible in case of pointers.
To allow temp objects passed to function, argument must be const reference.
class B{};
B f(){ return B()}; //return value
void f1(B&) {} //pass by ref
void f2(const B&) //pass by const ref

//! f1(f()); //Not OK, f() is temp object which requires to be passed as const ref
f2(f());  //OK, f2 takes const ref


Only a const member function may be called for a const object.

const with class:
Constructor Init List
class B{const int size;  public:B(int s); void print(); };
B::B(int s):size(s){}
void B::print(){cout<<size<<endl;}

B a(1), b(2), c(3);
a.print(); b.print(); c.print();

compile time constant inside class=static const: Simple const varible requires to be initialized along with declaration, but that is not possible until constructor is called. So how to achieve compile time const inside class?
Answer: static: only one instance regardless how many objects created. 

class StrStack{ static const int size=100; const string *stack[size]; int index;
public: StrStack(); void push(const string *s); const string *pop(); };

StrStack::StrStack():index(0){memset(stack, 0, sizeof(string*)*size); }
void StrStack::push(const string *s){if(index<size) stack[index++]=s;}


Sizeof Class
1. Empty class has size=1. This is to ensure all objects start unique address.
2. This is why new always return pointer to distinct objects.

class Empty {}; size=1
class Derived1 : public Empty {}; size=1
class Derived2 : virtual public Empty {}; size=8 (vptr)
class Derived3 : public Empty {   char c;  }; size=1
class Derived4 : virtual public Empty {     char c; }; size=16 (vtr, alignment)
class Dummy {     char c; }; size=1

Dynamic Memory in C++

malloc(), free() library functions and hence outside control of compiler.

operator new: reserve heap memory, call constructor
Type *fp=new Type(1,2); is equivalent to 

  • malloc(sizeof(Type)), check for return pointer not NULL
  • cast to object Type   [C++ does not allow void* to be assigned to any pointer.]
  • Call constructor of Type with malloc returned address passed as this pointer.

operator delete: calls destructor and release memory back to heap.
delete fp;  //If fp=0, nothing will happen. So safe site, make fp=0 after delete to prevent double delete.

Example
class Obj{
    void *data; const int size; const char id;
public:
    Object(int s, char c): size(sz), id(c) {  data=new char[size];  }
    ~Object() { delete []data; }
};

Object *a=new Object(10, 'z');   
delete a; //delete knows a is object pointer, so it calls destructor.

void *a=new Object(10, 'z');
delete a; //delete does not know that a is object, no destructor called. Silent memory leak of data.

For Arrays of Objects

Type *p=new Type[100];
delete p; //Not OK, remaining 99 left.
delete []p; //[] tells compiler to generate code that fetches number of objects to be freed and call destructor

Not good solution as pointer p can be changed to point to something else. Solution: int* const p; //sensible to make pointer const.

Note: Below statement, const binds to int, not to pointer:
         int const *q;  const int *q;

What when new cant find memory? new.h:new-handler is checked and if valid function pointer, it is called. 
To implement own new handler:
    #include <new>
    void out_of_memory() { cout<<"Mem over\n"; exit(1); }//at least does not silently die. New-handler function must take no arg, return void.
    main(){ set_new_handler(out_of_memory); ..}

What If new is overloaded, no new-handler is called by default unless implemented in user implementation of new.

Overloading new and delete operations prototypes:
void* operator new(size_t sz)
void operator delete(void* m)
Note: If want to add prints in your new, delete, do not use iostream functions (cout, cin, cerr0), as they use new to allocate objects and it would be deadlock.



What If: memory alllocated using malloc/calloc/realloc is attempted free using delete? undefined, because memory might be released without calling destructor

References
Thinking in C++, Bible of C++ by Bruce Eckel

No comments:

Post a Comment