It is very interesting for a language book to have a quick tour before everything. Actually, it is a great idea for people whose first language is not C++. People just don’t have to go through the basic things like control flows to know a language.
Generally, I have a feeling that C++’s code explain more itself. Programmers know more about what they are doing instead of checking documents for reference.
The Basics
Chapter 2 covers the very basic things (that would include control flows). First thing to note is that the introducing of namespace. Namespace in C++ seems to be clear and straightforward. What is not-so-straightforward is the name of header file/shared object library. Well, Java’s package infrastructure makes things more clear, though trying to put everything inside its structure probably is not so clever.
I didn’t expect C++ provides things like ‘auto’. (Well, not the ‘auto’ when I first use C language a few years ago.) Anyway, C++ is not a dynamic tool language. However, if you think about it, it is nothing runtime here, and you just need a compile time check. To be a real dynamic language, the thing you need is a flexible variable type (which is implemented in runtime even if you could do it in compile time) and generic programming for functions.
We finally have bools in C++!
C already have const. It was a beautiful promise that one should keep. constexpr does more than that. It calculates the value at compile time. When using constexpr as decorator of a function, it will evaluate at compile time when possible. Those function must be as simple as only having a return statement. Note that if a value is const, the compiler will assume you to keep the promise. Conclusion: Do keep your promise or you will have something unexpected.
Structs are improved. Say goodbye to the strange typedef!
Operators are functions, which they should be! Just need to have it in mind that if the value is lvalue or rvalue, to make the return type reference when necessary.
We also got a good syntax sugar for initiating fields. And a real enum class which is more than int.
C++ provides a more standard way for error handling. Exception introduced just like all of the other modern programming languages. Static assertions provide error handling on compile time.
There are some designing ideas. Invariant are restrictions applied to class definitions, and should be present in the code. If not, the functions should be independent rather than a member.
Abstract mechanism
As we can find in any articles about OOP, there is some mechanism in classes, like concrete/abstract class and class. There are some concerns under data storage since in C family you have to care more about memory, like the idea of the container. Also, note the following format for heap allocated arrays.
double *elem = new double[s]; delete[] elem;
Instead of making all functions virtual, we need to explicitly specify if a function is a virtual one. A class with at least a virtual function is called abstract class, which can not be instantiated. The mechanism behind overriding is vtbl. Usually, this is to add a 4 byte for pointer at the beginning of each object. If we want to call the virtual function in the base class, we can use scope resolution (::) operator to specify the class name, and this is called suppressed.
Constructor and destructor are called in a certain order. We don’t need to do anything for the constructor. However, for destructors, we need to make them virtual on base classes. Otherwise, only one of them is called.
An important designing idea for C++ is cautious against pointers. In C code, it is not strange for a function to return a raw pointer which points to something in heap. However, it is no longer recommended in C++. Destructors help us manage heap memory more freely. Moreover, we have unique_ptr as a smart pointer.
We need to note that “this” is a pointer, as it is in Java. Also, a const method is one that doesn’t change the object. Moreover, we can specify access-specifier when inheriting.
Copy and move. The default behavior of C++ copy is shallow copy since it just does a copy bitwise. To make it copy deeply, we just need to override operator =. Moreover, C++ provides the std::move mechanism.
std::move is a fast way to generate “right value reference”. In many cases, we don’t really need to copy the whole objects, especially when it is a container( such that copy is expensive. ) && is the mark for right value reference. When it is a parameter of a function, it (instead of the const &) can match the right value reference, to do a memory-economic behavior in the function.
If we don’t want certain behavior in the derived class, we can use the “=delete” mechanism.
class Shape{ public: //no copy Shape(const Shape&)=delete; Shape& operator=(const Shape&)=delete; //no move Shape(Shape &&)=delete; Shape& operator=(Shape &&)=delete; }
Template. Generic programming is very useful for OOP. C++ has the template grammar:
Single:
template<typename T>
Multiple:
template<typename T, typename U>
Variadic:
template<typename T, typename... Tail> void f(T head, Tail... tail){ g(head); f(tail...); }
To override the operator(), we can use it as a functional object. For instance
<typename T> class Less_than{ const T val; public: Less_than(const T &v): val(v){} bool operator()(const T &v) const {return v<val;} }
This is useful when constructing policy object for algorithms. Meanwhile, we can also use lambda expressions as policy objects. It will look like:
[&](int a){return a < x;}
while x is a local variable. & means capture all local variable as reference, and = mean all local variable as value. If we leave it blank, it will depends on it is used by value/reference.
Unlike C type typedef, we can use things like “using size_t = unsigned int;”. This is useful in template, and creating alias for C style types;
Containers and Algorithms
Standard library.
The standard library provides some useful tools, there is a list for some of them:
algorithm: copy(),find(),sort() array: array chrono: duration, time_point cmath: sqrt(),pow() complex: complex, sqrt(), pow() fstream: fstream, ifstream, ofstream future: future, promise iostream: istream, ostream, cin, cout map: map, multimap memory: unique_prt, shared_ptr, allocator random: default_random_engine, normal_distribution regex: regex, smatch string: string, basic_string set: set, multiset sstream: ostrstream, istrstream thread: thread unordered_map: unordered_map,unordered_multimap utility: move(), swap(), pair vector: vector
string
It is possible to concrete strings directly with ‘+’. This is useful for string, c-style string and chars.
strings length is str.length(), subscripting with [] and substring is str.substring(idx,l en), replaceable by str.replace(idx, len, new)
It is possible to compare string with string and string literals.
iostream
iostream operator << and >> is overloadable for user def types.
ostream& operator<<( ostream& os, const Entry &e); istream& operator>>(istream& is, Entry &e);
Containers: we can use a derived class for containers to do a range check by overloading operator[] and make use of vector(or sth else)<T>::at(i).
Iterator and algorithms
Iterator is used wisely in C++. The type name of an iterator is basetype::iterator. container.begin() is the first element of the container, while container.end() is the last one plus one. By using this, algorithms can be generic and we don’t have to remember details of implement for each type of containers/cases.
Note the flexible use of stream iterator.
Concurrency and Utilities
This part is basically a collection of utilities.
- thread
- unique_mutex
- shared_mutex
- async
- future/promise
- type functions
- pair/tuple
Concurrency
Thread is supported in C++. Creating a thread like thread td {f}
or thread td {F()}
with function f or function object F() will immediately start the thread. Call td.join
to wait for the thread.
We can pass parameters to thread using adiitional parameters in the thread constructor or making use of the constructor of the funtion object. Note that in method 1 it is passed by value by default, so we can use std::ref
to bypass it.
There is no way to return value directly, so we can either use a non-const reference, or pass a pointer to the location of res instead.
C++ has a built-in mutex. For mutex m
, use m.lock()
, m.try_lock()
and m.unlock()
. Meanwhile, it’s better to use unique_lock and shared_lock. unique_lock is created by unique_lock<mutex> lck{m};
. Lock is acquired until the object is distracted.
To avoid dead lock, we can pass an additional parameter defer_lock
to the constructor of unique_lock and lock all of them with lock(lck1,lck2,lck3)
.
this_thread
is referring the current thread. e.g. this_thread.wait_for(milisecond{20});
.
condition_variable object can wait
with a unique_lock as the parameter. It will release the lock and wait for notify
or notify_all
. This is useful in the producer-consumer model to avoid consume before product.
Communicating tasks. Without using thread and locks, several tools are availible. They are: future and promise, packaged_task and async(). They are in <future>.
future and promise looks like:
promise<X> px; future<X> fx = px.get_future(); X v = fx.get() //if necessary, wait.
On the other hand, we can use set_value and set_exception on the promise.
package_task is a good wrapper for future and promise. It looks like package_task<funtion_type> pt{f}
. While function type is f’s type like int(double*, double). Meanwhile, we can create future with constructor and pt’s get future. After that, we can use thread to run pt and get future’s value. (Use move on pt since it can’t be copied.)
async()
will return the future directly. By replacing thread by async, we don’t have to create package_task.
Utilities
unique_ptr and shared_ptr. Both of them are smart pointers. The difference is, unique_ptr is moved and shared_ptr is copied. When we actually need to copy and share the ptr, we will use it. Otherwise, we will use unique_ptr for polymorphic objects.
time utilities is in <chrono>. We can use high_resolution_clock::now() to get current time and use duration_cast<milliseconds> to cast, and count() to print cast result.
Type functions are those funtions eval in compile time and take type as parameter/return a type. Examples are numeric_limits<float>::min() and sizeof()
iterator_traits<Iter> can get iterators’ trait, that includes iterator_category, which shows if iterator is random or forward. For instance, if iterator is forward and we need random access, we can construct an vector using its begin()-Beg and end()-End, and copy it back using copy(vec.begin(),vec.end(),Beg).
Is_arithmetic<type>() can indicate if a type is arithmetic.
pair and tuple. pair is good for two element and can be make by make_pair(el1, el2) and its type is pair<El1,El2>. Member can be accessed by pr.first and pr.second. If you have more than two, you can use tuple. The only difference is to get, we have to use get<i>(tp).
regex. regex is defined by regex reg(R”str”); Use a smatch to catch the result, and ise regex_search(str,smts,reg) to search. Return value is if the reg is found.
Math. <cmath> has some functions like sqrt, log and sin. <numeric> has simple algorithm like accumulate(). e.g. accumulate(vec.begin(),vec.end(),0.0). There is also a complex in <complex>.
Random numbers. e.g.:
default_random_engine re; uniform_int_distribution<> one_to_six{1,6}; auto die = bind(one_to_six,re); die();
There is a valarray. Easy to apply mathematic on it, like add another valarray or divide by a num, or use things like abs or sqrt.
Conclusions
The strongest thought I have is that all though C++ has fully back support of C (before C99, I guess. We don’t have VLA in C++), some ideas have changed. Yes, you can write C++ code as “C with classes”, but that would be considered as “bad code” anyway. It is just normal to return pointers which point to somewhere in heap and programmers are confident with themselves. Those new design ideas in C++ involving strong resource safety are good things. After all, humans make mistakes and let’s avoid them by some high-level norm rather than overconfident from humans.
References are more useful than I thought. Well, I thought it is syntax sugars for pointers and people just make them because they are too lazy to use pointers. Now I think I was wrong. For instance, since some results of operators can be left value, can we really make the return type pointers? Yes, we can, by defaulting all return type pointers and make pointers to const to ensure the right value. However, I am very sure it will be a disaster for both those who write and read the code.
Using iterators are very brilliant. Iterators somehow look like pointers, with hiding the implementing details of the container. When writing algorithms, we don’t need to care about its memory size. Also, they make it easier to write algorithms, reducing confusion as other languages do.