A Tutorial for Real-Time C++
Total Page:16
File Type:pdf, Size:1020Kb
Appendix A A Tutorial for Real-Time C++ This appendix presents a short tutorial on C++. It is not intended to be a complete language tutorial, but rather a brief introduction to the most important parts of C++ for programming real-time embedded systems. A.1 C++ Cast Operators C++ has four template cast operators. The code below, for instance, uses the static_cast operator to cast from float to int. float f = 3.14159265358979323846F; int n=static_cast<int>(f); // The value is 3 The code sequence below uses the reinterpret_cast operator to set bit–5 in the microcontroller port register portb. // The address of portb is 0x25. constexpr std::uint8_t portb = UINT8_C(0x25); // Cast std::uint8_t to std::uint8_t*. volatile std::uint8_t* pb = reinterpret_cast<volatile std::uint8_t*>(portb); // Set portb.5. *pb |= UINT8_C(0x20); The reinterpret_cast operator is sometimes considered unsafe because it can convert unrelated types. For a detailed description of the potential dangers of © Springer-Verlag Berlin Heidelberg 2015 319 C. Kormanyos, Real-Time C++, DOI 10.1007/978-3-662-47810-3 320 Appendix A: A Tutorial for Real-Time C++ reinterpret_cast, see Eckel [1], Chap. 3, in the subsection on reinterpret_cast. For direct memory access in microcontroller program- ming, however, reinterpret_cast can be considered safe and appropriate. This book only uses the static_cast and reinterpret_cast cast oper- ators. C++ also has the dynamic_cast and const_cast operators. The dynamic_cast operator converts pointers and references. It also performs a costly but robust runtime check to ensure that the result of the cast is valid. The const_cast operator can change the constness or volatile qualification of an object by either setting or removing its const or volatile attribute. A.2 Uniform Initialization Syntax C++ has a syntax for fully uniform type initialization that works on any object. It was introduced with C++11. Uniform initialization syntax can be used along- side traditional constructor initialization with parentheses and initialization with operator= alike. Uniform initialization syntax uses curly braces to hold the initial values. The code below, for instance, initializes built-in types with uniform initialization syntax. int n { 123 }; float f { 3.1415926535’8979323846F }; Aggregate types can also be initialized with uniform initialization syntax. The code below initializes a structure with two data members. struct my_struct { int n; float f; my_struct(const int n_ = 0, const float& f_ = 0.0F) : n(n_), f(f_) { } }; my_struct instance { 123, // Initial value of n. 3.14159265358979323846F // Initial value of f. }; Appendix A: A Tutorial for Real-Time C++ 321 In certain situations the compiler can also deduce the type of an object based on uniform initialization syntax. For example, struct my_struct { // ... }; my_struct function() { // The compiler correctly deduces the return type. return { 456, 0.5772156649’0153286061F }; } Uniform initialization syntax can be used in the constructor initialization list of a class type as well as to initialize an instance of a class type. For instance, struct point { point(const int x_ = 0, const int y_ = 0) : x{x_}, y{y_} { } int x; int y; }; point pt { 123, 456 }; In addition, uniform initialization syntax can be used to conveniently initial- ize STL containers such as std::array and std::vector (Sect. A.6). Some examples are shown below. 322 Appendix A: A Tutorial for Real-Time C++ std::array<int, 3U> a { {1,2,3} }; std::vector<char>v { { ’a’, ’b’, ’c’ } }; A.3 Overloading Function overloading in C++ allows for the creation of several functions with the same name but different types of input and output parameters. For example, // The area of a rectangle. float area(const float& length, const float& width) { return length * width; } // The area of a circle. float area(const float& radius) { constexpr float pi = 3.14159265358979323846F; return (pi * radius) * radius; } Global functions and local functions as well as class member functions can be overloaded. It is essential, however, not to confuse class member overloading with dynamic polymorphism and the runtime virtual function mechanism, described in Sect. 4.4. Appendix A: A Tutorial for Real-Time C++ 323 A.4 Compile-Time Assert The static_assert facility checks a constant expression at compile time. The syntax of static_assert is static_assert(expression, message); Here, expression is a condition to be checked by the compiler and message contains potentially useful diagnostic text. If the result of expression tests true, then static_assert does nothing. Compilation continues unabatedly. If the result of expression tests false, then a compiler error ensues and the message text is shown like a regular compiler error. static_assert can be used to perform compile-time diagnostics. This can be convenient for checking platform-specific requirements. For example, constexpr unsigned int version = 3U; // Print error message if version is less than 2. static_assert(version >= 2U, "Version is too low!"); In this example, static_assert ensures that version is 2 or higher and issues a compiler error if not. A.5 Numeric Limits The C++ standard library supports numeric limits of built-in types in its <limits> header. The <limits> library provides the std::numeric_limits template and provides specializations for both built-in floating-point and integer types as well as bool. The member variable is_specialized is true for a specialization of std::numeric_limits. The synopsis of the std::numeric_limits template class is shown below. namespace std { template<class T> class numeric_limits { public: static constexpr bool is_specialized = false; static constexpr T min () { return T(); } static constexpr T max () { return T(); } 324 Appendix A: A Tutorial for Real-Time C++ static constexpr T lowest() { return T(); } static constexpr int digits = 0; static constexpr int digits10 = 0; static constexpr int max_digits10 = 0; static constexpr bool is_signed = false; static constexpr bool is_integer = false; static constexpr bool is_exact = false; static constexpr int radix = 0; static constexpr T epsilon() { return T(); } static constexpr T round_error() { return T(); } static constexpr int min_exponent = 0; static constexpr int min_exponent10 = 0; static constexpr int max_exponent = 0; static constexpr int max_exponent10 = 0; static constexpr bool has_infinity = false; static constexpr bool has_quiet_NaN = false; static constexpr bool has_signaling_NaN = false; static constexpr float_denorm_style has_denorm = denorm_absent; static constexpr bool has_denorm_loss = false; static constexpr T infinity () { return T(); } static constexpr T quiet_NaN () { return T(); } static constexpr T signaling_NaN() { return T(); } static constexpr T denorm_min () { return T(); } static constexpr bool is_iec559 = false; static constexpr bool is_bounded = false; static constexpr bool is_modulo = false; static constexpr bool traps = false; static constexpr bool tinyness_before = false; static constexpr float_round_style round_style = round_toward_zero; }; } The specialization of std::numeric_limits for int on a platform with 32–bit int, for example, might be implemented as follows. Appendix A: A Tutorial for Real-Time C++ 325 namespace std { template<> class numeric_limits<int> { public: static constexpr bool is_specialized = true; static constexpr int min() { return 0; } static constexpr int max() { return +2147483647; } static constexpr int lowest() { return -2147483648; } static constexpr int digits = 32; static constexpr int digits10 = 9; static constexpr int max_digits10 = 9; static constexpr bool is_signed = false; static constexpr bool is_integer = true; static constexpr bool is_exact = true; static constexpr int radix = 2; static constexpr int epsilon() { return 0; } static constexpr int round_error() { return 0; } static constexpr int min_exponent = 0; static constexpr int min_exponent10 = 0; static constexpr int max_exponent = 0; static constexpr int max_exponent10 = 0; static constexpr bool has_infinity = false; static constexpr bool has_quiet_NaN = false; static constexpr bool has_signaling_NaN = false; static constexpr float_denorm_style has_denorm = denorm_absent; static constexpr bool has_denorm_loss = false; static constexpr int infinity () { return 0; } static constexpr int quiet_NaN() { return 0; } static constexpr int signaling_NaN() { return 0; } static constexpr int denorm_min() { return 0; } static constexpr bool is_iec559 = false; static constexpr bool is_bounded = false; 326 Appendix A: A Tutorial for Real-Time C++ static constexpr bool is_modulo = false; static constexpr bool traps = false; static constexpr bool tinyness_before = false; static constexpr float_round_style round_style = round_toward_zero; }; } The std::numeric_limits templates allow the programmer to query infor- mation about the numeric limits of built-in types. For example, constexpr int n_max = std::numeric_limits<int>::max(); Numeric limits can be conveniently used in other templates. For example, template<typename unsigned_type> struct hi_bit { // The bit-position of the high bit. static constexpr int bpos = std::numeric_limits<unsigned_type>::digits - 1; // The value of the type with the high-bit set. static constexpr unsigned_type value = static_cast<unsigned_type>(1) << bpos; }; The scalable hi_bit template structure provides compile-time constant values. For instance, constexpr std::uint8_t hi08 = hi_bit<std::uint8_t>::value; // (1 << 7) constexpr std::uint16_t hi16 = hi_bit<std::uint16_t>::value;