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对于string,vector,map这些类型,我们常常采用passing by reference,而不是直接传递值。& my_vector);
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类型。使用容器时,一个不好的做法是存放指向自定义类型对象的指针,比如下面的代码
#includeclass 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 }
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_ptr
. Ashared 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) 函数指针的使用
#includedouble 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