Chapter 11 Interacting with the User and System
System Call: A special type of function call that transfers control from the program into the operating system. When system calls fail in C, they set a global variable called errno, The perror function can print a descriptive error message based on errno. Note that since errno is global, you must take care not to call anything (such as printf) that might change it before you test it or call perror.

The left figure depicts the conceptual relationship between the program, C library, operating system, and hardware. C code can make system calls directly or use functions in the C library. The C library functions then make system calls as they need. The OS then interacts with the hardware appropriately.
int main(int argc, char ** argv, char ** envp){
//....
}
When the command shell or other program wants to run another program, it makes a couple of system calls:
- It makes a call to create a new process(fork)
- The new process makes another system call(execve) to replace its running program with the requested program. The execve takes three arguments: the first argument specifies the file with the binary to run, a second argument specifying the values to pass the new program as argv (must end with a NULL), and a third argument specifying the values to pass for envp
- OS destroys the currently running program and loads the specified executable binary into memory. It writes the values of argv and envp into memory in a pre-agreed-upon format.
- The kernel sets the execution arrow to a starting location specified in the executable binary.
- This startup code residing in an object file that is linked with any C program you compile calls various functions that initialize the C library. This startup code also counts the elements of argv to compute argc and eventually calls main.
- When main returns, it returns to the function that called it. In the case of main, the caller is the startup code.
- The startup code then performs any cleanup required by the C library and calls exit, passing in the return value of main as the argument of exit.
- The shell or other program can make a system call that waits for its child processes to exit and collects their return values.
File
The FILE structs do not contain the name of the file, but rather a file descriptor which is a numeric identifier returned to the program by the OS when the program makes the system call to open the file(open system call). The C library functions that operate on the stream pass this descriptor to the relevant system calls, which perform the underlying input/output operations for them.
When writing to a file fails, it can be detected later, because the C library functions may buffer the data until there is significant data that can be written at once.
stdin: 0 ; stdout: 1; stderr: 2
Note: Never use the gets function.
FILE * fopen(const char * filename, const char * mode);
//int not char, so as to read EOF
int fgetc(FILE * stream);
//an assignment is also an expression that evaluates to the value that is assigned
while( (c = fgetc(f)) != EOF ){
//...
}
//write data to str[0], str[1]..., the size of array is defined by size
//succeed: return str; fail: return NULL
char * fgets(char * str, int size, FILE * stream);
//ptr: pinter to the data to write
//size: size of each item
//nitems: how many such items should be read from the stream
size_t fread(void * ptr, size_t size, size_t nitems, FILE * stream);
//use fgets or getline and sscanf to replace fscanf
int fprintf(FILE *stream, const char *format, ...);
//fputs would not do any format conversions
int fputs(const char *str, FILE *stream);
size_t fwrite(const void * ptr, size_t size, size_t nitems, FILE* stream);
int fclose(FILE * stream);
Chapter 17 Templates
Template can be used to achieve parametric polymorphism.
template<typename T, typename S>
int someFunction(const T & t, S s){
//...
}
template<unsigned int S>
struct Vector{
unsigned char bytes[S];
};
Vector<3> test;
We can think of a template as taking parameters and creating a function. And template parameters may be types, which are specified with typename. Whenever you instantiate a template, the C++ compiler creates a template specialization.
Template parameters can also be another template, then we can decide which container use contain which type.
template<typename T, template<typename> typename Container>
class A{
public:
Container<T> data;
};
template<typename T>
class C{
public:
T* datas;
};
int main(){
A<int, C> a;
}
//A is a template whose second parameter is another template.
//We can just replace T, Container by the input parameters:
//Container<T> data; => C<int> data;
We can see typename T as T is a normal type such as int. float, etc, and if there are something else before typename, then T’s type is decided by the previous words.
Template Rules
- Template definitions must be “visible” at instantiations
The implementation of the templated function or class must be written in the header files. Because before the compiler instantiates the template, the template is not a function, but a recipe to create the actual function. The compiler needs to have access to the implementation of the methods to instantiate them with the template argument. If these implementations were not in the header, they wouldn’t be accessible, and therefore the compiler wouldn’t be able to instantiate the template. - Template arguments must be compile-time constant
- Templates are only type checked when specialized
Due to this role, you can write a template that can only legally be instantiated with some types, but not others. The first time the function encounters an instantiation of the templated function with a particular set of arguments, it specializes the function. For a class, the compiler specializes the part of the class definition that is required to make the object’s in-memory representation, the (non-virtual methods) of a templated class are only specialized when the corresponding method is used. - Multiple close brackets must be separated by whitespace
vector<pair<int, string> > myVector;
- Dependent type names require keyword typename
T::x, Something<T>::x
, in these two situations, we call x a dependent name, as its interpretation depends on what T is. Whenever you use a dependent name for a type, you need to explicitly tell the compiler that it names a type by placing the typename keyword before the name of the type. - You can provide an explicit specialization
Often explicit specialization is performed to provide a more efficient implementation of the exact same behavior as the “primary template”. - Template parameters for functions may be inferred
When you use a templated function, you can omit the angle brackets and template arguments in cases where the compiler can infer them. But this behavior is not recommended. For templated classes, the arguments must always be explicitly specified.
Function object
Overloading the function call operator ( ‘()’ ) on a class allows the programmer to place parentheses with arguments after an instance of that class in a way that looks like a function call, an object with “function call” behavior(i.e., an overloaded function call operator) is referred to as a function object.
class OrderIgnoreCase{
public:
bool operator()(const string & s1, const string & s2) const{
for(size_t i = 0;i < s1.size() && i < s2.size(); i++){
char c1 = tolower(s1[i]);
char c2 = tolower(s2[i]);
if(c1 != c2){
return c1 < c2;
}
}
return s1.size() < s2.size();
}
};
std::min<string, OrderIgnoreCase>(str1, str2, OrderIgnoreCase());
Chapter 18 Inheritance
Inheritance can express is-a relationship.
class BankAccount{
};
class InvestmentAccount: public BankAccount{
};
public inheritance: The access should be unchanged from that declared in the parent class.
private inheritance: All inherited members become private in the child class. The child class can’t visit the private members in the parent class but can access them through functions inherited from the parent class.
protected inheritance: The public members of the parent class become protected members of the child class. Members of a class may be declared protected, which means that they may be accessed by members of the class or by members of any of its child classes. In the child class, it can only access the protected members through a pointer/reference/variable of the child class’s own type.
In C++, the type of an object actually changes during the construction process. Assume class A <- B <- C, when instantiating C, the new operator allocates space for an entire C object, but the type of the object is initially set to A, after the constructor for A finishes, the type changes to B, and finally C. During the destruction, the process happens in reverse, however, it stops if any parent class’s destructor is trivial. After the destruction of C, if B’s destructor is nontrivial, the type of object changes from C to B once control enters B’s destructor.
To call some other constructor explicitly, we should write the call to the parent class’s constructor as the first element of the initializer list.
class BankAccount{
BankAccount(double b){}
};
class InvestmentAccount: public BankAccount{
public:
InvestmentAccount(double balance) : BankAccount(balance){}
}
Subtype Polymorphism
polymorphism: Allows the same code to operate on multiple types.
Subtype polymorphism allows the programmer to treat an instance of a child class as if it were an instance of one of its parent classes. Polymorphism is only applicable when used with pointers or references, the reason is a matter of implementation, the space of the parent class would be different from the space of the child class, however, pointers are the same size regardless of what they point to.
Since inheritance reflects an “is-a” relationship, the fact that a B “is-an” A means that the fact that we can use a B as an A is quite natural.
The static type is the type obtained by the type-checking rules of the compiler, which only uses the declared types of variables. The dynamic type of the object is the type of object that is actually pointed at. The compiler only works with static types.
class A{
};
class B:public A{
void hello(){}
};
A* a = new B();
a->hello();
//The compiler will think a points at A, and find A does't have hello() method,
//then see it as an error.
The approach of having the static type determine which method to call is called static dispatch, which is the default behavior in C++ (better performance).
class A{
void hello(){
cout<<"A"<<endl;
}
};
class B:public A{
void hello(){
cout<<"B"<<endl;
}
};
A* a = new B();
a->hello();
//the output is A, a's static type is A, dynamic type is B, however, the static type was
//used to determine the method to call.
To transform from static dispatch to dynamic dispatch, we should declare the methods as virtual in the parent class, and once a method is declared virtual, it remains virtual in all child classes. When the compiler sees a->hello(), it would go back to class A to check whether it is static dispatch or dynamic dispatch by the keyword virtual.
Classes that contain virtual methods are never POD types, an object with at least one virtual method has an extra field that is particular to its type.
During object construction and destruction, the dynamic type of an object changes, when the object calls virtual methods in construction and destruction methods, the compiler will choose the method based on the dynamic dispatch.
In C++, whenever you use a class that may participate in polymorphism, its destructor should be declared virtual.
- If you want to call the parent class’s version of a method, you can do so by explicitly requesting it with the fully qualified name.
- An overridden method may have a more permissive access restriction, and it cannot become more restrictive.
- An overridden method may change the return type in a covariant fashion-meaning that the return type in the subclass is a subtype of the return type in the superclass.
class Animal{
public:
virtual Animal* getFather(){}
};
class Cat{
public:
virtual Cat* getFather(){}
}
Abstract Methods and Classes
class Shape{
public:
virtual bool containsPoint(const Point& p) const = 0;
};
Abstract methods must be virtual, as it only makes sense to use them with dynamic dispatch. When a class has an abstract method in it, that class becomes an abstract class.
- An abstract class cannot be instantiated.
- Any subclass of an abstract class is also abstract unless it defines concrete implementations for all abstract methods in its parents.
These two rules work together to make an important guarantee to the compiler: Any object you actually instantiate will have an implementation for all of the methods declared in it.
Inheritance and Template
Templates are not fully composable with inheritance, mostly with respect to the rules that relate to virtual methods.
1. Aspects that are composable:
template<typename T>
class MyFancyVector: public std::vector<T>{
};
//MyFancyVector<int> will inherit from std::vector<int>
2. Aspects that are not composable:
- A templated method cannot be virtual
- A templated function cannot override an inherited method
- Virtual methods are specialized when an instance is made
Chapter 19 Error Handling and Exceptions
C-Style Error Handling
- It’s easy to forget.
- It “clutters up” the code
- We only know that an error has occurred and have no additional information about what went wrong.
C++style: Exceptions
- We want to remove the possibility that an error can be silently ignored.
- We would like error handling to be as unobtrusive as possible in our code.
- The ability to be able to convey arbitrary information about the problem.
Once an exception is thrown, it propagates up the call stack, forcing each function to return, until a suitable exception handler is found. Each time a stack frame is destroyed, destructors are invoked as usual to clean up objects. However, if one of the destructors throws an exception that propagates out of the destructor, the program crashes.
In C++, we can throw any type of object, but should generally only throw subtypes of std::exception. To use std::exception, you should #include <exception>
, there are also a variety of built-in subtypes of std::exception, which typically require #include <stdexcept>
.
try{
//code that might throw
}
catch(std::exception & e){
//code to handle a generic exception
}
When an exception is thrown:
- The exception object is potentially copied out of the frame into some location that will persist through handling. The compiler may eliminate the copy if it does not change the program’s behavior but may arrange for the unnamed temporary to be directly allocated into some other part of the memory.
- If the execution arrow is currently inside of a try block, then it jumps to the close curly brace of the try block and begins trying to match the exception type against the handler types in the order they appear. If not, go to step 3. If the execution arrow encounters a catch block capable of catching the exception type that was thrown, the exception is considered caught, and the process continues in step4. If no handler is found, step 2 repeats.
- The exception propagates out of the function it is in, the execution arrow then returns to wherever the function was called, and step 2 repeats.
- Bind(a reference to) the exception to the variable name declared in the () of the catch block. If a statement of the form
throw;
is encountered inside the catch block, then the exception being handled is re-thrown, and the exception handling process starts again from step 2 – no extra copy is made. - When the execution arrow reaches the close curly brace of the handler, the program is finished handling the exception, the exception object is deallocated, and execution continues normally at the statement immediately following the close curly brace.
For the catch block, one can specify that it will catch any type, but it cannot bind the exception to a variable.
try{
}
catch(...){
//code to handle any exception
}
For a try block, there is a variation called a function try block, which is primarily of use in a constructor, where the programmer wants to catch exceptions that may occur in the initializer list. When an exception occurs during the initialization, since the object cannot be properly initialized, the exception is automatically re-thrown. (as if throw;
were the last line of the function try block.)
class X{
Y aY;
Z aZ;
public:
X() try : aY(42), aZ(0){
//constructor's body here as normal
}
catch(std::exception & e){
//code to handle the exception
}
};
Exceptions as part of a function’s interface:
int f(int x) throw(std::bad_alloc, std::invalid_argument);
int g(int* p, int n) throw(); //throw no exceptions
int h(int z); //throw any type of exception
When overriding a method that provides an exception specification, the overriding method must have an exception specification that is the same or more restrictive than the exception specification of the inherited method.
//same
int f(int x) throw(std::bad_alloc, std::invalid_argument);
//more restrictive
int f(int x) throw(std::bad_alloc);
int f(int x) throw();
The reason for this restriction arises from polymorphism.
Comment