QuantStart网站的学习笔记——C++ Inplementation

C++  Inplementation

1.virtual destructor,使用virtual destructor主要是为了防止memory leak。如果析构函数没有声明为虚函数,那么当我们定义一个指向派生类类对象的基类指针(派生类对象本身也是基类对象),然后撤销这一指针所所指向的对象时,我们调用的实际上是基类的析构函数而不是派生类的虚构函数。这会引起memory leak。比如下面的代码

class Base {
public:
 Base();
 ~Base();
};

class Derived : public Base {
private:
  double val;
public:
 Derived(const double& _val);
 ~Derived();
}

void do_something() {
 Base* p = new Derived;
 // Derived destructor not called!!
 delete p;  
}

这时, val is not deallocated. 回顾纯虚函数,非虚函数和非纯虚函数。

2.passing by values vs. pass by reference

回顾使用引用类型形参的三种情形,避免复制,要改变实参,以及数据类型本身不允许进行值传递(比如数组)

double euclid_norm(const vector& my_vector);
对于string,vector,map这些类型,我们常常采用passing by reference,而不是直接传递值。

3.Mathematical constants

 cmath library包含了很多mathematical constants。为了使用这些常数,我们在包含头文件之前首先要使用宏定义(we need to use a #define macro called_USE_MATH_DEFINES and add it before importing the cmath library):

#define _USE_MATH_DEFINES
 
#include 
#include 
 
int main() { 
  std::cout << M_PI << " " << M_E << " " << M_SQRT2 << endl;
  return 0;
}

下面列举了一些比较重要的常数。

Mathematical Expression C++ Symbol Decimal Representation
pi M_PI 3.14159265358979323846
pi/2 M_PI_2 1.57079632679489661923
pi/4 M_PI_4 0.785398163397448309616
1/pi M_1_PI 0.318309886183790671538
2/pi M_2_PI 0.636619772367581343076
2/sqrt(pi) M_2_SQRTPI 1.12837916709551257390
sqrt(2) M_SQRT2 1.41421356237309504880
1/sqrt(2) M_SQRT1_2 0.707106781186547524401
e M_E 2.71828182845904523536
log_2(e) M_LOG2E 1.44269504088896340736
log_10(e) M_LOG10E 0.434294481903251827651
log_e(2) M_LN2 0.693147180559945309417
log_e(10) M_LN10 2.30258509299404568402

Note that it is not best practice within C++ to use #defines for mathematical constants! Instead, as an example, you should use const double pi = 3.14159265358979323846;. The #defines are a legacy feature of C.

对于C++而言,使用宏定义来处理数学常数并不是最好的做法。C++中我们常常使用const关键字,而#define是C的特点。

4.STL containers and auto_ptrs

我们常常定义自己的数据类型,并用容器存放该类型的对象,比如我们定义了Option类型。使用容器时,一个不好的做法是存放指向自定义类型对象的指针,比如下面的代码

#include 

class Option {
public:
  ..
}

void vec_option(std::vector<Option*>& vec)
{
  vec.push_back(new Option());

  // .. Some additional code ..

  delete vec.back();    // Skipping this line causes a memory leak
  vec.pop_back();       // Causes a dangling pointer if this line isn't reached
}
如果去除delete那句代码,就会抛出异常,引起memory leak或dangling pointer 
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。
尽管上面的代码可以成功编译,但是这种pointer的使用方式会使得function变得fragile,我们有更好的方法代替这种dumb pointer。smart pointer是   std::auto_ptr<> .

#include 
#include    // Needed for std::auto_ptr<>

class Option {
public:
  ..
}

void vec_option(std::vector<std::auto_ptr<Option> >& vec)
{
  vec.push_back(new Option());
  ..
  vec.pop_back();
}

但是如果我们运行上面的代码会出现compling error。因为std::auto_ptr<> does not fulfill the requirements of being copy-constructible and assignable. 

而作为容器存放的对象,必须满足copy-constructible and assignable. 即支持赋值操作和复制构造函数。

因此,我们得另想办法。在C++ 11标准之前,解决这一问题的最好的方法是利用Boost library smart pointers. 我们使用Boost library shared pointer -boost:shared_ptrAshared pointer不存在memory leak的问题,因为shared pointer不会在vector中迭代,并且calling delete for each item.

#include 
#include <boost/shared_ptr.hpp>  // Need to include the Boost header for shared_ptr

class Option {
public:
  ..
}

typedef boost::shared_ptr<Option> option_ptr;   // This typedef stops excessive C++ syntax later

void vec_option(std::vector& vec)
{
  option_ptr ptr_opt(new Option());  // Separate allocation to avoid problems if exceptions are thrown
  vec.push_back(ptr_opt);
  ..
  vec.pop_back();
}

为什么使用shared_ptr就可以避免memory leak和悬垂指针呢?因为Shared pointers make use of reference counting, which ensures that the allocation option object (ptr_opt) is correctly transferred into the vector, in this line:vec.push_back(ptr_opt);

在C++ 11中,不支持auto_ptr的使用,而是shared_ptr的使用也被纳入了C++ 11.

std::shared_ptr is included in the "memory" header

#include 
#include   // std::shared_ptr is included in the "memory" header

class Option {
public:
  ..
}

typedef std::shared_ptr<Option> option_ptr;   // This typedef stops excessive C++ syntax later

void vec_option(std::vector& vec)
{
  option_ptr ptr_opt(new Option());  // Separate allocation to avoid problems if exceptions are thrown
  vec.push_back(ptr_opt);
  ..
  vec.pop_back();
}

5.回顾genericFunction,以及函数指针作为类的成员的应用。Payoff类的设计,NonlinearSolver类的设计。抽象类的使用。

6.Function Objects in C++

We will strike a balance between re-use, efficiency and maintainability when modelling functions in C++, so as to improve productivity without creating complicated code. 有一些常见的例子,比如Payoff, Matrix Algebra, Differential Equation Coefficients

1) 函数指针的使用

#include 

double add(double left, double right) {
    return left + right;
}

double multiply(double left, double right) {
    return left * right;
}

double binary_op(double left, double right, double (*f)(double, double)) {
    return (*f)(left, right);
}

int main( ) {
    double a = 5.0;
    double b = 10.0;

    std::cout << "Add: " << binary_op(a, b, add) << std::endl;
    std::cout << "Multiply: " << binary_op(a, b, multiply) << std::endl;

    return 0;
}

上面即为函数指针实现genericFunction的例子。回顾泛型编程的定义,模板以及两种多态性,编译多态性和运行多态性,回顾多态类(至少含有一个virtual function)。

但是函数指针也有一些缺点,比如效率不高,解决这一问题的方法比如使用functors,比如对于模板的支持不好,而且adaptation不强。为了解决这一问题,我们可以利用C++ function object 即functors.

2) functors的使用

function object也称为functor,用class或struct都可以,因为function object是利用constructor和对operator()做overloading,而这些都是public的,所以大部分人就直接使用struct,可少打public:这几个字。

函数对象,形式上是经一个类实例化的对象本质上是为了实现某种与函数等价的功能。函数对象的思想是:用类来封装一个函数功能,然后用这个类实例化不同的函数对象。

C++ function object的定义和好处:

A function object allows an instance object of a class to be called or invoked as if it were an ordinary function. In C++ this is carried out by overloading operator(). The main benefit of using function objects is that they are objects and hence can contain state, either statically across all instances of the function objects or individually on a particular instance.

回顾类的静态成员和类的静态成员函数。类的静态成员的初始化是在类体外进行的,类的静态成员并不是类的对象的组成部分,而是类的一部分。类的静态成员函数并不含有隐含的this指针,且能访问类的静态成员。

下面就是function object,或者更准确说function object hierarchy的例子

#include 

// Abstract base class                                                                                                                                                                                                  
class BinaryFunction {
public:
  BinaryFunction() {};
  virtual double operator() (double left, double right) = 0;
};

// Add two doubles                                                                                                                                                                                                      
class Add : public BinaryFunction {
public:
  Add() {};
  virtual double operator() (double left, double right) { return left+right; }
};

// Multiply two doubles                                                                                                                                                                                                 
class Multiply : public BinaryFunction {
public:
  Multiply() {};
  virtual double operator() (double left, double right) { return left*right; }
};

double binary_op(double left, double right, BinaryFunction* bin_func) {
  return (*bin_func)(left, right);
}

int main( ) {
  double a = 5.0;
  double b = 10.0;

  BinaryFunction* pAdd = new Add();
  BinaryFunction* pMultiply = new Multiply();

  std::cout << "Add: " << binary_op(a, b, pAdd) << std::endl;
  std::cout << "Multiply: " << binary_op(a, b, pMultiply) << std::endl;

  delete pAdd;
  delete pMultiply;

  return 0;
}

在上面的例子中,我们使用了抽象类(无法实例化的类),动态绑定。回顾动态绑定的两个条件,虚函数和指向基类对象的指针。因为抽象类无法实例化,所以我们利用指针将pAdd and pMultiply给新的binary_op function. 新的binary_op函数的第三个形参不再是函数指针,而是指向基类的指针类型。上面的例子非常清晰地阐述了function object即functor。

之后在利用genericFunction的地方,我们都可以利用抽象类,继承层次设计,动态绑定机制以及function object更好地实现原本的目的。使用functor时,我们用类来封装一个函数功能,然后经类实例化不同的函数对象。再次强调,函数对象,形式上是经一个类实例化的对象,本质上是为了实现某种与函数等价的功能。其好处是

An obvious question is "What do we gain from all this extra code/syntax?". The main benefit is that we are now able to add state to the function objects. For Add and Multiply this is likely be to unnecessary. However, if our inheritance hierarchy were modelling connections to a database, then we might require information about how many connections currently exist (as we wouldn't want to open too many for performance reasons). We might also want to add extra database types. An inheritance hierarchy like this allows us to easily create more database connection classes without modifying any other code.

我们可以利用function object来设计Payoff类。回顾Payoff基类往往也设计为抽象类。

原理类似,这里我只是简单写个例子,

//Payoff.h
class Payoff
{
public:
	Payoff(){}; //constructor千万不要遗漏了{}
	virtual double operator()(double K, double S) = 0;
};


//用类来封装一个函数功能
class Call_payoff : public Payoff
{
public:
	Call_payoff(){};
	virtual double operator()(double K, double S)
	{	
		return S - K > 0 ? S - K : 0;
	}
};


class Put_payoff : public Payoff
{
public:
	Put_payoff(){};
	virtual double operator()(double K, double S)
	{
		return K - S > 0 ? K - S : 0;
	}
};
//main.cpp
#include 
#include "Payoff.h"

using namespace std;

double payoff(double K, double S, Payoff *ptr)
{
	return (*ptr)(K, S);
}


int main()
{
	double K = 35.0;
	double S = 40.0;


	Payoff *pCall = new Call_payoff(); //经类实例化不同的函数对象,通过函数对象来实现函数的功能
	Payoff *pPut = new Put_payoff();


	std::cout << "Call Option Payoff: " << payoff(K, S, pCall) << std::endl;
	std::cout << "Put Option Payoff: " << payoff(K, S, pPut) << std::endl;


	return 0;
}


7.C++ STL container/iterator

回顾迭代器是作为算法和数据类型之间的媒介

iterator的类型,1)只读不写迭代器input iterator,只支持++,不支持--,2)output iterator可以修改迭代器所指向的元素的值且不能stepping backwards. 前两种迭代器读和写只能是一次 3)forwar  读写多次,但是只能stepping forward 4)bidirectional


你可能感兴趣的:(C++)