C++的一些优化操作

来源:互联网 发布:bitspirit mac 编辑:程序博客网 时间:2024/06/06 01:35

1.1.1  利用C++语言的优化机制

1.1.1.1 使用带赋值操作的变量声明

void assign(const C& c1)

{

 C c2;

 c2 = c1; // 有三个函数调用:一次默认构造函数、一次赋值函数和一次析构函数

}

void initialize(const C& c1)

{

 C c2 = c1; // 只有一次拷贝构造函数和一次析构函数

}

 

 

1.1.1.2 使用时再声明对象

错误的用法

is_C_Needed()返回false时,c1被白白构造了一次!

正确的用法

bool is_C_Needed();

void use()

{

  C c1;

  if (is_C_Needed() == false)

  {

    return; //c1 was not needed

  }  

  //use c1 here

  return;

}

void use()

{

  if (is_C_Needed() == false)

  {

    return; //c1 was not needed

  }  

  C c1; //moved from the block's beginning

  //use c1 here

  return;

}

 

 

1.1.1.3 使用成员初始化列表

使用成员初始化列表

class Person

{

private:

  C c_1;

  C c_2;

public:

  Person(const C& c1, const C& c2 ): c_1(c1), c_2(c2) {}

};

在拷贝构造函数中初始化

Person::Person(const C& c1, const C& c2)

{

 c_1 = c1;

 c_2 = c2;

}

测试代码

int main()

{

  C c; //created only once, used as dummy arguments in Person's constructor

  for (int j = 0; j<30000000; j++)

  {

    Person p(c, c);

  }

  return 0; 

}

测试结果

初始化方式

调用默认构造函数的次数

调用拷贝构造函数的次数

调用赋值函数的次数

调用析构函数的次数

使用成员初始化列表

0

60,000,000

0

60,000,000

拷贝函数中初始化

60,000,000

0

60,000,000

60,000,000

 

1.1.1.4 使用前向操作符(即使用前向++/--

因为后向操作符会返回一个临时对象,所以使用前向操作符的效率更高:

class Date

{

private:

  //...

  int AddDays(int d);

public:

  Date operator++(int unused);

  Date& operator++();

};

Date Date::operator++(int unused)  //postfix

{

  Date temp(*this); //create a copy of the current object

  this->AddDays(1); //increment  current object

  return temp; //return by value a copy of the object before it was incremented

}

Date& Date::operator++()   //prefix

{

  this->AddDays(1); //increment  current object

  return *this; //return by reference the current object

}

 

1.1.1.5 inline函数

inline函数具有函数展开的特征,同时又带有安全的类型信息(宏定义没有类型信息),它通过空间来换得时间,所以适当的使用inline函数可以提高代码的效率。

但是,一般只有简短的存取操作(比如getset操作)、函数包装等会使用inline,同时能够得到效率的提高。如果一个代码量大的函数使用了inline,会导致执行程序的大小增大,甚至造成CPUcache命中率降低。

此外,使用inline函数还需要注意,如果inline函数改变了(包括它使用的数据类型改变了),那么所有用到了这个头文件的函数必须重新编译。

1.1.1.6 避免临时对象

错误的代码

void foo(T t);

……

T t;

foo(t); // 编译器会在栈上构造一个临时对象,并将t拷贝过去

另外有种写法也是常见的错误:

void foo(T& t);

……

const T t;

foo(t); // 为了防止在foo中修改t,编译器也会在栈上构造一个临时对象

将对象作为函数返回值也是一种产生临时对象的现象:

string T() { ……; }

 

……

string s;

s = foo();

 

典型的是实现operator +,因为按照其语义,必须返回一个传值的对象;所以在使用 string + 时要注意,最好用 +=或者 append() 等。

类似vector在重新分配空间时,也会导致临时对象的产生:

比如以前是100个元素,现在需要插入10个元素,那么vector会分配至少110元素的空间,将这100个元素拷贝过去,接着在这100个元素原有的空间上调用其析构函数,总共会有110次拷贝构造函数和100次析构函数的调用。

正确的代码

尽量使用引用传递参数,如果不允许修改参数则使用const,例如:

void foo(T& t) 或者 void foo(const T& t)

 

1.1.1.7 其它提高速度的技巧

1、  使用位域来节省空间,比如:

struct BillingRec

{

……

  unsigned call: 3; //three bits

  unsigned tariff: 2; //two bits

};

2、当函数的参数太多的时候,可以使用一个类或者结构来封装这些参数;

3、将不改变的数据声明为常量;

4、使用仿函数而非函数指针,因为仿函数可以做inline展开;

5、禁止RTTI和异常处理;

1、  注意字节对齐问题

class CLarger     // 占用 20字节的类

{

    bool b1;

    int i1;

    bool b2;

    int i2;

    bool b3;

};

 

class CSmaller    // 占用 12 字节的类

{

    int i1;

    int i2;

    bool b1;

    bool b2;

    bool b3;

};