v4if / blog

:octocat: 用issues写博客,记录点滴
34 stars 7 forks source link

Advanced C++ #2

Open v4if opened 7 years ago

v4if commented 7 years ago

const

// reference cannot be reassigned
string& m_name = "Hello";

// A compile time constraint that an object can not be modified 
const int i = 9;
i = 6  //assignment of read-only variable 'i'

const int *p1 = &i;  //data is const, pointer is not 
int* const p2 = &i;  //pointer is const, data is not
const int* const p3; //data and pointer are both const

// If const is on the left of *, data is const
// if const is on the right of *, pointer is const
int const *p4 = &i;
const int *p5 = &i;

// cast is not a good thing
const_cast<int&>(i) = 6;

int j;
static_cast<const int&>(j);

const used with functions

class Dog {
    int age;
    string name;

public:
    Dog() {
        age = 3;
        name = "dummy";
    }

    // const parameters
    void setAge(const int& a) { age = a; }

    // const return value
    const string& getName() {return name;}

    // const function
    // this function will not change any of the member valuables of this class
    // a const function can only call another const function
    void printDogName() const { cout << name << endl; }
    //can be overload
    void printDogName() const { cout << name << endl; }
};

int main() {
  Dog d;

  int i = 9;
  d.setAge(i);

  const string& n = d.getName();

  const Dog d2;
  d.printDogName();
  d2.printDogName();
}
class BigArray {
  vector<int> v; // huge vector
  int accessCounter;

  int* v2;  //another int array

public:
  int getItem(int index) const {
    //accessCounter++;
    const_cast<BigArray*>(this)->accessCounter++;
    return v[index];
  }

  // const can be remove
  void setV2Item(int index, int x) const {
    *(v2 + index) = x;
  }
};

Compiler Generated Functions

Compiler silnetly writes 4 functions if they are not explicitly declared

  1. Copy constructor
  2. Copy Assignment Operator
  3. Destructor
  4. Default constructor (only if there is no constructor declared)
class dog {};

/* equivalent to  */
class dog {
  public: 
    dog(const dog& rhs) {...}; // Member by member initialization
    dog& operator=(const dog& rhs) {...}; // Member by member copying
    dog() {...};  //1. Call base class's default constructor;
                     //2. Call data member's default constructor;
    ~dog() {...}; //1. Call base class's destructor;
                       //2. Call data member's destructor;
};

Disallow Functions

class OpenFile{
public:
  OpenFile(string filename) { cout << "Open a file " << filename << endl;}
  OpenFile(OpenFile& rhs) = delete;
  //or
/*
private:
  OpenFile(OpenFile& rhs);  // no function body
**/
};

int main() {
  OpenFile f(string("Bo_file"));

  // wrong
  OpenFile f2(f);
}
class OpenFile{
public:
  OpenFile(string filename) { cout << "Open a file " << filename << endl;}
  void destroyMe() { delete this; }

private:
  ~OpenFile() {cout << "OpenFile destructed."  << endl;}
};

int main() {
  // a class like OpenFile which has a private destructor can only be stored on heap,
  // it can not be stored on stack
  OpenFile* f = new OpenFile(string("Bo_file"));
  f->destroyMe();
}

Summary

  1. C++ 11: f() = delete;
  2. C++ 03: Declare the function to be private, and not define it.
  3. Private destructor: can only be stored on heap. stay out of stack.

Virtual Destructor and Smart Destructor

/* Use virtual destructors in polymorphic(多态) base classes */
// base class
class Dog {
public: 
  // this class will be used polymorphically which means most likely it will need a virtual destructor
  virtual void bark() {}
  virtual ~Dog() { cout << "Dog destroyed." << endl; }
};

class Yellowdog : public Dog {
public: 
  ~Yellowdog() { cout << "Yellow dog destroyed." << endl; }
};

class DogFactory {
public:
  static Dog* createYellowDog() { return (new Yellowdog()); }
  // ... create other dogs
};

int main() {
  Dog* pd = DogFactory::createYellowDog();
  //... Do something with pd

  delete pd;
  return 0;
}
class Dog {
public: 
  ~Dog() { cout << "Dog destroyed." << endl; }
};

class Yellowdog : public Dog {
public: 
  ~Yellowdog() { cout << "Yellow dog destroyed." << endl; }
};

class DogFactory {
public:
  static shared_ptr<Dog> createYellowDog() { 
    return shared_ptr<Yellowdog>(new Yellowdog()); 
  }
  // ... create other dogs
};

int main() {
  shared_ptr<Dog> pd = DogFactory::createYellowDog();
  //... Do something with pd

  return 0;
}

Notes:

All classes in STL have no virtual destructor, so be careful inheriting from them.

Exceptions in Destructors

class dog {
public: 
  string m_name;
  dog(string name) { m_name = name; cout << name << " is born." << endl;}
  ~dog() { cout << m_name << " is destroyed." << endl;}
  void bark() {...}
};

int main() {
  try {
    dog dog1("Henry");
    dog dog2("Bob");
    throw 20;
    dog1.brak();
    dog2.bark();
  } catch (int e) {
    cout << e << " is caught." << endl;
  }
}

output: the stack will unwind and all the local variables inside the try block needs to be destroyed.

Henry is born. Bob is born. Bob is destroyed. Henry is destroyed. 20 is caught.

// c++ doesn't like having work more than one exception pending at the same time
// so it will just crash
~dog() {
  try {
    // Enclose all the exception prone code here
  } catch (MYEXCEPTION e) {
    // Catch exception
  } catch (...) {
  }
}

// or 
class dog {
public: 
  string m_name;
  dog(string name) { m_name = name; cout << name << " is born." << endl;}
  ~dog() { cout << m_name << " is destroyed." << endl;}
  void prepareToDestr() {...; throw 20;}
  void bark() {...}
};
int main() {
  try {
    dog dog1("Henry");
    dog dog2("Bob");
    dog1.brak();
    dog2.bark();

    dog1.prepareToDestr();  // goto catch
    dog2.prepareToDestr();
  } catch (int e) {
    cout << e << " is caught." << endl;
  }
}

Virtual Function [dynamic binding]

class Dog {
public: 
  Dog() { cout << "Dog born." << endl; }
  virtual void bark() { cout << "I am just a dog" }
  void seeCat() { bark(); }
};

class Yellowdog : public Dog {
public: 
  Yellowdog() { cout << "Yellow dog born." << endl; }
  virtual void bark() { cout << "I am a yellow dog" << endl; }
};

int main() {
  Yellowdog d;
  d.seeCat();

  return 0;
}

Summary

Should avoid calling virtual function in a constructor or destructor.

Handling Self-Assignment

dog dd;
dd = dd; // Looks silly

dogs[i] = dogs[j]; // self assignment
class collar;
class dog {
  collar* pCollar;
  dog& operator=(const dog& rhs) {
    if (this == &rhs) {
      return *this;
    }

    collar* pOrigCollar = pCollar;
    pCollar = new collar(*rhs.pCollar);
    delete pOrigCollar;
    return *this;
  }
};

//or
class dog {
  collar* pCollar;
  dog& operator=(const dog& rhs) {
    *pCollar = *rhs.pCollar;  // member by member copying of collars or
                                          // call collar's operator=
    return *this;
  }
};

Resource Acquisition Initialization

// not good 
Mutex_t mu = MUTEX_INITIALIXZER;

void functionA()
{
  Mutex_lock( &mu );
  //...
  Mutex_unlock( &mu ); // will this line always be executed?
}
class Lock {
  private:
    Mutex_t* m_pm;
  public: 
    explicit Lock(Mutex_t* pm) { Mutex_lock(pm); m_p = pm; }
    ~Lock() {Mutex_unlock(m_pm);}
};

void functionA()
{
  Lock mylock(&mu);
  // ...
  // The mutex will always be released when mylock is destroyed from stack.
}
int functionA() {
  // The default value for D is "delete"
  shared_ptr<dog> pd(new dog());
  //...
  // The dog is destructed when pd goes out of scope
}
class Lock {
private:
  shared_ptr<Mutex_t> pMutex;
public:
  explicit Lock(Mutex_t *pm) : pMutex(pm, Mutex_unlock) {
    Mutex_lock(pm);
  // The second parameter of shared_ptr constructor is "delete" function.
  }
};

Lock L1(&mu);
Lock L2(L1);

Strcut Vs Class

// Data container
struct Person_t {
  string name;  // public
  unsigned age;
};

// Complex Data Structure
class Person {
  string name_;  //private
  unsigned age_;
};

Notes: avoid using too many of setter and getter function

Resource Managing Class

class Person {
public:
    Person(string name) { pName_ = new string(name); }
    ~Person() { delete(pName_); }
    void printName() { cout << *pName_ << endl;}

    // 构造拷贝
    Person(const Person& rhs) {
        // make a deep copy
        pName_ = new string(*(rhs.pName()));
    }
    // 赋值拷贝
    Person& operator=(const Person& rhs) {
        *pName_ = *rhs.pName();
        return *this;
    }

    string* pName() const { return pName_; }

private:
    string* pName_;
};
TEST(leetcode, advanced_test_resource_manage_class) {
    vector<Person> persons;
    persons.push_back(Person("Bob"));
    // 1. "Bob" is constructed
    // 2. A copy of "Bob" is saved in the vector persons (shallow copy)
    // 3. the copy of "Bob" is destroyed
    persons.back().printName();

    Person b = Person("nick");
    b.printName();

    cout << "Bye" << endl;
}
class Person {
public:
    Person(string name) { pName_ = new string(name); }
    ~Person() { delete(pName_); }
    void printName() { cout << *pName_ << endl;}

    string* pName() const { return pName_; }
    Person* clone() {
        return (new Person(*pName_));
    }

private:
    string* pName_;
    Person(const Person& rhs);
    Person& operator=(const Person& rhs);
};
TEST(leetcode, advanced_test_resource_manage_class) {
    vector<Person*> persons;
    persons.push_back(new Person("Bob"));

    persons.back()->printName();

    cout << "Bye" << endl;
}

Summary : one object owning another object through its pointer

Virtual Constructor - Clone() Function

class Dog {
public:
  virtual Dog* clone() { return (new Dog(*this)); }
};

class YellowDog : public Dog {
  virtual YellowDog* clone() { return (new YellowDog(*this)); }
};

void foo(Dog* d) {  //d is a Yellowdog
  // Dog* c = new Dog(*d); // c is a Dog
  Dog* c = d->clone();  //c is a YellowDog
}

int main() {
  YellowDog d;
  foo(&d);
}

Type Conversion

Implicit 隐含的、Explicit 明确的 [casting]

// implicit
char c = 'A';
int i = c;  // Integral promotion

char* pc = 0; // int -> Null pointer

void f(int i);
f(c);

dog* pd = new YellowDog(); // pointer type conversion
operator string() const { return name_; }
/* static_cast */
int i = 9;
// convert object from one type to another
float f = static_cast<float>(i);

// Type conversion needs to be defined
dog d1 = static_cast<dog>(string("Bob"));

// convert pointer/reference from one type to a related type (down/up) cast
dog* pd = static_cast<dog*>(new yellowdog());
/* dynamic_cast */
// 1. It convert pointer/reference from one type to a related type (down cast)
// 2. run-time type check. If succeed, py == pd; if fail, py == 0;
// 3. It requires the 2 types to be polymorphic (have virtual function)

/* all yellowdogs are dogs, but not all dogs are yellowdog */
dog* pd = new yellowdog();
yellowdog* py = dynamic_cast<yellowdog*>(pd);
/* const_cast */
// Only works on pointer/reference
// Only works on same type, cast away constness of the object being pointed to

const char* str = "Hello, world.";
char* modifiable = const_cast<char*>(str);
/* reinterpret_cast */
// The ultimate cast that can cast one pointer to any other type of pointer

long p = 51110980;
dog* pd = reinterpret_cast<dog*>(p);
/* C-Style Casting */
short a = 200;
int j = (int)a;
int j = int(a);

Inheritance - Public, Protected, and Private

/* They specifies different access control from the derived class to the base class */

class B {};

class D_pub : public B {};  // ''is-a'' relation
class D_priv : private B {};  // has-a
class D_prot : protected B {};

/*
 Access Control:
 1. None of the derived classes can access anything that is private in B.
 2. D_pub inherits public members of B as public and the protected members of B as protected.
 3. D_priv inherits public and protected members of B as private.
 4. D_prot inherits public and protected members of B as protected.

 Casting:
 1. Anyone can cast a D_pub* to B*. D_pub is a special kind of B.
 2. D_priv's members and friends can cast a D_priv* to B*.
 3. D_prot's members, fiends, and childrens can cast a D_prot* to B*.
 */

public:
  using dog::bark;

/*
 1. Precise definition of classes.
 2. Don't override non-virtual functions.
 3. Don't override default parameter values for virtual functions.
 4. Force inheritance of shadowed functions.
 */

Understanding rvalue and lvalue

/*
 Simplified Definition:
 lvalue - An object that occupies some identifiable location in memory
 "内存中,有特定位置(地址)的东西"或"指向一个确定对象的东西"

 rvalue - Any object that is not a lvalue
 */

Dynamic Polymorphism

struct TreeNode { TreeNode *left, *right; };
class Generic_Parser {
public: 
  void parse_preorder (TreeNode* node) {
    if (node) {
      process_node(node);
      parse_preorder(node->left);
      parse_preorder(node->right);
    }
  }

private:
  virtual void process_node(TreeNode* node){}
};

class EmployeeChart_Parser : public Generic_Parsesr {
private:
  void process_node(TreeNode* node) {
    cout << "Customized process_node() for EmployeeChart." << endl;
  }
};

int main() {
  ...
  EmployeeChart_Parser ep;
  ep.parse_preorder(root);
}

Summary:

  1. the memory cost of the virtual table
  2. the runtime cost of dynamic binding

Static Polymorphism

template<typename T> class Generic_Parser {
public: 
  void parse_preorder(TreeNode* node) {...}
  void process_node(TreeNode* node) {
    static_cast<T*>(this)->process_node(node);
  }
};

class EmployeeChart_Parser : public Generic_Parser<EmployeeChart_Parser> {
public:
  void process_node(TreeNode* node) {
    cout << "Customized process_node() for EmployeeChart." << endl;
  }
};

int main() {
  ...
  EmployeeChart_Parser ep;
  ep.parse_preorder(root);
  ...
  // 以空间换时间
  MilitaryChart_Parser mp;
}
template<typename T>
T Max(vector<T> v) {
  T max = v[0];
  for (typename std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    if (*it > max) {
      max = *it;
    }
  }
  return max;
}

Multiple Inheritance

// 菱形继承
class File {                 //                 File
public:                        //               /        \
  string name;             //  InputFile        OutputFile
  void open();              //               \        /
};                                 //               IOFile

class InputFile : public File {
public: 
  void read();
};

class OutputFile : public File {
public: 
  void write();
};

class IOFile : public InputFile, public OutputFile {};

int main() {
  IOFile f;
  f.InputFile::name = "file1";
  f.OutputFile::name = "file2";
}
class File {               
public:                      
  string name;          
  void open();             
};                                 
// only needs one instance of File
class InputFile : virtual public File {
public: 
  void read();
};

class OutputFile : virtual public File {
public: 
  void write();
};

class IOFile : public InputFile, public OutputFile {};

int main() {
  IOFile f;
  f.open();
}
class File {               
public:                      
  string name;          
  File(string fname);          
};                                 

class InputFile : virtual public File {
public: 
  InputFile(string fname) : File(fname) {}
};

class OutputFile : virtual public File {
public: 
  OutputFile(string fname) : File(fname) {}
};

class IOFile : public InputFile, public OutputFile {
  IOFile(string fname) : InputFile(fname), OutputFile(fname), File(fname) {}
};

int main() {
  IOFile f;
}
/*
 Abstract class : A class has one or more pure virtual functions
 Pure abstract class : A class containing only pure virtual functions
   - no data
   - no concrete functions 
 */
class OutputFIle {
public:
  void write() = 0;
  void open() = 0;
};

Namespace and Keyword "using"

/*
 * C++ Keyword: using
 * 1. using directive : to bring all namespace members into current scope 
 */
using namespace std;

/*
 * 2. using declaration
 *   a. Bring one specific namespace member to current scope
 *   b. Bring a member from base class to current class's scope
 */
using std::cout;
cout << "Hello world.\n";

class B {
public: 
  void f(int a);
};
class D : private B {
public: 
  using B::f;  // only using in class scope
};
int main() {
  D d;
  d.f(8);
}
/*
 * Anonymous Namespace
 */
// accessed with the same file
namespace {
  void h() {std::cout << "h()\n";}
}
// static viod h() {std::cout << "h()\n";}
int main() {
  h();
}
v4if commented 7 years ago

当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称悬垂指针(也叫迷途指针)。

某些编程语言允许未初始化的指针的存在,而这类指针即为野指针。

v4if commented 7 years ago

new int[] 是创建一个int型数组,数组大小是在[]中指定,例如: int * p = new int[3]; //申请一个动态整型数组,数组的长度为[]中的值

new int()是创建一个int型数,并且用()括号中的数据进行初始化,例如: int *p = new int(10); // p指向一个值为10的int数。

v4if commented 7 years ago

int *next = new int[patt.length() + 1];

sizeof()与strlen()的区别,strlen()不计算'\0'

v4if commented 7 years ago
// 1-(size_t)2 无符号 正最大
// 易产生bug
for (int j = i; j < s.length() - len; j = j + len) {} 
for (int j = i; j + len < s.length(); j = j + len) {}
v4if commented 7 years ago
// 容易导致缓冲区溢出的字符串处理函数
char * gets ( char * str );
char * strcat ( char * destination, const char * source );
char * strcpy ( char * destination, const char * source );
int sprintf ( char * str, const char * format, ... );

// 替代函数
char * fgets ( char * str, int num, FILE * stream );
char * strncat ( char * destination, const char * source, size_t num );
char * strncpy ( char * destination, const char * source, size_t num );
int snprintf ( char * s, size_t n, const char * format, ... );

snprintf(buff, sizeof(buff), "%s\r\n", ctime(&ticks));
v4if commented 7 years ago

AddressSanitizer 检查内存越界访问和使用已释放的内存等问题

To compile: g++ -O -g -fsanitize=address use-after-free.c

v4if commented 7 years ago

一些容易误解的函数

// 参数是秒
unsigned int sleep(unsigned int seconds);
// 第二个参数是子串长度!而非终结字符
string substr (size_t pos = 0, size_t len = npos) const;
v4if commented 7 years ago

std::thread my_thread(background_task()); 这里相当于声明了一个名为my_thread的函数,这个函数带有一个参数(函数指针指向没有参数并返回background_task对象的函数),返回一个std::thread对象的函数,而非启动了一个线程

// 使用多组括号
std::thread my_thread((background_task()));

// 或使用新统一的初始化语法
std::thread my_thread{background_task()};

// 使用lambda表达式,允许使用一个可以捕获局部变量的局部函数
std::thread my_thread([]{
  do_something();
});
v4if commented 7 years ago

如std::map<>,这里列出了三个常见关联容器的方式:

  1. 二叉树,比如:红黑树
  2. 有序数组
  3. 哈希表
v4if commented 7 years ago

assert() 编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真

v4if commented 7 years ago

如果你的类可以被non-trivial继承,就该有个virtual destructor。这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。 并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

v4if commented 7 years ago

覆写、隐藏与重载的区别