c++ notes

c++ notes, from books, keep updating...

Posted by thomasliao on April 10, 2021

c++ Notes

1.0.0 编译器

  • msvc
    • windows
    • 最历史悠久、最成熟,但也是最有历史包袱的编译器,微软
    • 模板的支持则是它的软肋
    • 2018 年宣布已全面支持 C++17 标准
  • gcc
    • 跨平台
    • GCC 的第一个版本发布于 1987 年,是由自由软件运动的发起人 Richard Stallman(常常被缩写为 RMS)亲自写的
    • 由于 GCC 是用 GPL 发布的,任何对 GCC 的修改都必须以 GPL 协议发布
    • 应用最广的自由软件之一
  • clang
    • 跨平台
    • 错误信息的友好性是它的最大亮点
    • 最新, LLVM项目的一部分
    • Clang 目前在 macOS 下是默认的 C/C++ 编译器

1.0.1 内联函数

  • 如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗
  • 关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用
  • inline只适合涵数体内代码简单的函数数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。
  • 内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率
  • 最好将内联函数定义放在头文件中

1.0.2 macro

  • #define 预处理指令用于创建符号常量:#define PI 3.14159
    • #define 来定义一个带有参数的宏,注意要多加括号:MIN(a,b) (a<b ? a : b)
  • 条件编译:#ifdef #define #endif
  • “#”置于identifier面前表示将identifier变成字符串字面值,“##”连接

1.0.3 array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned cnt = 42;
constexpr unsigned sz = 42;

int arr[10];
//if you don't know how many elements you want, use a vector
int *parr[sz];

const unsigned size = 3;
int ia1[size] = {0, 1, 2};
int a2[] = {0, 1, 2};
int a3[5] = {0, 1, 2};
string a4[4] = {"hi", "bye"}; // remember #include <string>
string Animals[4] = {"Elephant", "Fox", "Lion", "Tiger"}; // remember #include <string>
//char array is special
char a1[] = {'C', '+', '+'}; //list initialization, no null;
CHECK_V(sizeof(a1) == 3)
char a22[] = {'C', '+', '+', '\0'}; //list initialization, explicit null
CHECK_V(sizeof(a22) == 4)
char a33[] = "C++"; //null terminator added automatically
CHECK_V(sizeof(a33) == 4)
const char a44[7] = "Daniel";

无法通过变量来声明数组大小

ex:

1
int state[max] = {0};
  • You can declare an array only with constant size, which can be deduced at compile time. variables’ values can be known only at runtime.
  • To elaborate, when you allocate memory on the stack, the size must be known at compile time. Since the arrays are local to the method, they will be placed on the stack.

solution1:

  • You can either use constant value, or allocate memory in the heap using new, and deallocate when done using delete, like
1
2
3
    int* zod1 = new int[zo1];
//.... other code
    delete[] zod1;

solution2:

  • use vector instead of array here also, and vector will take care of allocation on the heap.
  • As a side note, you should not pass vector by value, as the whole vector will be copied and passed as argument, and no change will be visible at the caller side.

Use vector& zodis1 instead

1.0.4 casting

  • static_cast
    • any well-defined type conversion, other than those involving low-level const
  • dynamic_cast
    • supports the run-time type identification
    • 继承
  • const_cast
    • changes only a low-level const in its operand
  • reinterpret_cast
    • performs a low-level reinterpretation of the bit pattern of its operands

1.0.5.C++ , size of int, long, short

decided by compiler

  • depends on the compiler and only on the compiler.
  • Hardware/OS is of no importance at all
  • The compiler is free to implement a hardware abstraction layer of any thickness and emulate absolutely anything

char , byte, octet

  • char is always a byte , but it’s not always an octet
    • byte
      • A byte is the smallest addressable unit of memory (in most definitions),
    • octet
      • an octet is 8-bit unit of memory
  • sizeof(char) is always 1 for all implementations
  • CHAR_BIT macro in limits.h
    • defines the size of a byte for a platform
    • it is not always 8 bit.
      • 16-bit, 32-bit bytes
      • others[ex. 7], have to deal with aligned and padding

size

  • range
    • signed char: -127 to 127 (note, not -128 to 127; this accommodates 1’s-complement and sign-and-magnitude platforms)
    • unsigned char: 0 to 255
    • “plain” char: same range as signed char or unsigned char, implementation-defined
    • signed short: -32767 to 32767
    • unsigned short: 0 to 65535
    • signed int: -32767 to 32767
    • unsigned int: 0 to 65535
    • signed long: -2147483647 to 2147483647
    • unsigned long: 0 to 4294967295
    • signed long long: -9223372036854775807 to 9223372036854775807
    • unsigned long long: 0 to 18446744073709551615
  • rules
    • char, signed char, and unsigned char are at least 8 bits
    • signed short, unsigned short, signed int, and unsigned int are at least 16 bits
    • signed long and unsigned long are at least 32 bits
    • signed long long and unsigned long long are at least 64 bits

ref

3.const–use const whenever possible

type declaration before or after const are the same

tips: read from right to left

1
2
3
4
5
6
7
8
9
10
11
//type declaration before or after const are the same

//tips: read from right to left
int const *ptr; //ptr is a pointer to constant int
const int *ptr1; //prt1 is a pointer to an int who is constant

int *const ptr3; //ptr3 is a const pointer to an int

//both ptr4 and ptr5 are constant pointer to a constant int
int const *const ptr4;
const int *const ptr5;

stl iterators are modeled on pointers

tips: read from left to right

1
2
3
4
5
6
7
8
9
    //const pointer
    const std::vector<int>::iterator iter = vec.begin();
    *iter = 10; //ok
    //++iter; //error

    //pointer to const data
    std::vector<int>::const_iterator clter = vec.begin();
//     *clter = 10; //error
    ++clter;   //ok

Declaring something const helps compilers detect usage errors.

  • const can be applied to objects at any scope,
    • function parameters
    • return types,
    • member functions as a whole.

4.make sure that objects are initialized before used

initialized is not guaranteed

1
2
3
4
5
6
int x; //guaranteed to be initialized to zero

class Point {
    int x, y;
};
Point p; // p's data members are sometimes guaranteed sometimes are not
  • rules are complicated(not worth)
    • in the c part of c++ , initialization would incur a runtime cost, not guaranteed
      • array isn’t guaranteed to be initialized
    • non-c parts of c++, might be guaranteed
      • a std::vector is guaranteed to be initialized

not confuse assignment with initialization

  • assignment : inside the body of constructor
  • initializatio took place earlier
    • default constructors were automatically called prior to entering the body
    • but built-in type
      • no constuctor/destructor
      • no guarantee it was initialized prior to its assignment
  • The rules of C++ stipulate that data members of an object are initialized before the body of a constructor is entered

use the member initialization list instead of assignments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//ex.
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
//these are now all initializations
: theName(name), // copy-constructed
theAddress(address), //  copy-constructed
thePhones(phones),//  copy-constructed
numTimesConsulted(0)
{} // the ctor body is now empty

// use the member initialization list even when you want to default-construct a data member
ABEntry::ABEntry()
: theName(), // call theName’s default ctor;
theAddress(), // do the same for theAddress;
thePhones(), // and for thePhones;
numTimesConsulted(0) // but explicitly initialize
{} // numTimesConsulted to zero
  • more efficient
    • assigiment first called default constructors then assigned new value
      • all the deafult constructors’ work are wasted
    • member initialization list avoids it
      • arguments in the initialization list are used as constructor arguments for the various data members
        • For most types, a single call to a copy constructor is more efficient — sometimes much more efficient — than a call to the default constructor followed by a call to the copy assignment operator
        • for built-it types, no different between initialization and assignment
          • for consistency, use member initialization too.
        • Sometimes the initialization list must be used,, even for built-in types.
          • For example, data members that are const or are references must be initialized; they can’t be assigned
    • if too much data members
      • multiple initialization lists introduces undesirable repetition (in the lists) and boredom
      • moving the assignments to a single (typically private) function that all the constructors call

order of data initialization

  • base classes are initialized before derived classes
  • and within a class,data members are initialized in the order in which they are declared
    • even if they are listed in a different order on the member initialization list
    • to avoid confusion: list members in the initialization list in the same order as they’re declared in the class

staitc object

  • A static object is one that exists from the time it’s constructed until the end of the program.
    • excluded:Stack and heap-based objects
    • Included are
      • global objects,
      • objects defined at namespace scope,
      • objects declared static inside classes,
      • objects declared static inside functions,
      • objects declared static at file scope

problem

  • the relative order of initialization of nonlocal static objects defined in different translation units is undefined
  • it’s not only impossible to determine the right order of initialization,
  • it’s typically not even worth looking for special cases where it is possible to determine the right order.

solution: replaced non-local static objects with local static objects.

  • move each non-local static object into its own function, where it’s declared static
    • Singleton pattern
  • This approach is founded on C++’s guarantee that local static objects are initialized when the object’s definition is first encountered during a call to that function.
  • As a bonus, if you never call a function emulating a non-local static object, you never incur the cost of constructing and destructing the object,
    • something that can’t be said for true non-local static objects
  • might cause multi-theads problem
    • one solution is to manually invoke all the reference-returning functions during the single-threaded startup portion of the program.

5.funcitons c++ silently writes and calls

  • default constructor, copy constructor, destructor, copy assignment operator(=)
    • copy constructor, =
      • simply copy each non-static data member
        • class for its own copy construtor
        • primitive just copy bits
      • compiler won’t generate constructor if written constructors manually
        • neither for others
  • generated only if they are needed

6.Explicitly disallow the use of compiler generated functions you do not want

ex. avoid to copy

  • the copy constructor and the copy assignment operator are declared private and are not defined
  • change to compile error
    • use a base class: Uncopyable
1
2
3
4
5
6
7
8
class Uncopyable {
protected: // allow construction
	Uncopyable() {} // and destruction of
	~Uncopyable() {} // derived objects...
private:
	Uncopyable(const Uncopyable&); // ...but prevent copying
	Uncopyable& operator=(const Uncopyable&);
};

7.virtual destructor

  • problem: when a derived class object is deleted through a pointer to a base class with a non-virtual destructor, results are undefined.
    • tipicially, the derived part is never destroyed
    • leading to partialy destroyed object
  • but
    • gratuitously[无偿的, 免费的] declaring all destructors virtual is wrong
      • increase the size
    • should not inherent from any class lacking a virtual destructor–not designed to be used polymorphically
      • std::string
      • stl containers: vector, list, set, tr1::unordered_map
      • C++ offers no derivation-prevention mechanism
        • java final
        • c# sealed class
  • solution: give the base class a virtual destructor.
    • destroy the entire object
    • should provide a body for the destructor
    • how it work:
      • the most derived classes’s destructor is called first
      • then the destructor of eache base class is called
1
2
3
4
5
6
class AWOV { // AWOV = “Abstract w/o Virtuals”
public:
virtual ~AWOV() = 0; // declare pure virtual destructor
};

AWOV::~AWOV() {} // definition of pure virtual dtor

8.Prevent exceptions from leaving destructors.

  • Destructors should never emit exceptions.
    • swallow it
    • terminate the program: std::abort()
  • If class clients need to be able to react to exceptions thrown during an operation,
    • server swallow the exception, and mark the exception with var
    • client check the var, and continue to solve it