编译器拷贝优化(copy elision)

Copy Elision是大多数编译器都会执行的一项优化。gcc的手册里面是这样描述的:

-fno-elide-constructors
The C++ standard allows an implementation to omit creating a temporary which is only used to initialize another object of the same type. Specifying this option disables that optimization, and forces G++ to call the copy constructor in all cases.

当需要创建一个只是用来初始化其他对象的临时对象时,编译器可以忽略此操作,不执行拷贝构造函数(copy-initialization),而是直接把临时对象创建在目标对象的空间上(direct-initialization),从而提高程序的性能。例如:
[ccb_c++]
class foo
{
public:
foo() {cout<<"ctor"<如何利用拷贝优化

假设我们需要写这样一个函数:
[ccb_c++]
int f1(const foo &a)
{
foo tmp(a);
do_something(tmp);

return 0;
}
[/ccb_c++]
为了避免改变参数,函数f1生成一份参数的拷贝,更改复制得到的对象。其实这样做并没有什么好处,既然复制操作无法避免,那就让它来的自然点好了。
[ccb_c++]
int f2(foo a)
{
do_something(a);

return 0;
}
[/ccb_c++]
与f1相比,f2的好处在于,当参数是右值的时候,拷贝操作被省略了。如果参数是左值,那么两个函数都会进行一次拷贝。

但有一点需要注意,如果函数将参数返回,那么RVO将不会起作用。
[ccb_c++]
foo f3(foo a)
{
do_something(a);

return a;
}
[/ccb_c++]
其实这也很容易理解,编译器在编译使用此函数的代码时,必定会使用两个地址,一个存放按值传递的参数,另一个存放返回值。在返回的时候,一定执行一次拷贝构造函数,将参数复制到返回值的地址上。更详细的解释参见Why is RVO disallowed when returning a parameter?

即使不能利用RVO,我们也可以将函数优化。一般而言,默认构造函数和swap操作会比拷贝构造函数更快,所以可以这样写:
[ccb_c++]
foo f4(foo a)
{
do_something(a);
foo tmp;
tmp.swap(a);

return tmp;
}
[/ccb_c++]

参考

  1. The Name Return Value Optimization
  2. C++: Exploiting Copy Elisions
  3. Why is RVO disallowed when returning a parameter?
  4. Is RVO (Return Value Optimization) guaranteed for all objects in gcc compilers?

[译]详解C++右值引用

C++0x标准出来很长时间了,引入了很多牛逼的特性[1]。其中一个便是右值引用,Thomas Becker的文章[2]很全面的介绍了这个特性,读后有如醍醐灌顶,翻译在此以便深入理解。

目录

  1. 概述
  2. move语义
  3. 右值引用
  4. 强制move语义
  5. 右值引用是右值吗?
  6. move语义与编译器优化
  7. 完美转发:问题
  8. 完美转发:解决方案
  9. Rvalue References And Exceptions
  10. The Case of the Implicit Move
  11. Acknowledgments and Further Reading

概述

右值引用是由C++0x标准引入c++的一个令人难以捉摸的特性。我曾偶尔听到过有c++领域的大牛这么说:

每次我想抓住右值引用的时候,它总能从我手里跑掉。

想把右值引用装进脑袋实在太难了。

我不得不教别人右值引用,这太可怕了。

右值引用恶心的地方在于,当你看到它的时候根本不知道它的存在有什么意义,它是用来解决什么问题的。所以我不会马上介绍什么是右值引用。更好的方式是从它将解决的问题入手,然后讲述右值引用是如何解决这些问题的。这样,右值引用的定义才会看起来合理和自然。

右值引用至少解决了这两个问题:

  1. 实现move语义
  2. 完美转发(Perfect forwarding)

如果你不懂这两个问题,别担心,后面会详细地介绍。我们会从move语义开始,但在开始之前要首先让你回忆起c++的左值和右值是什么。关于左值和右值我很难给出一个严密的定义,不过下面的解释已经足以让你明白什么是左值和右值。

在c语言发展的较早时期,左值和右值的定义是这样的:左值是一个可以出现在赋值运算符的左边或者右边的表达式e,而右值则是只能出现在右边的表达式。例如:

[ccb_c++]
int a = 42;
int b = 43;

// a与b都是左值
a = b; // ok
b = a; // ok
a = a * b; // ok

// a * b是右值:
int c = a * b; // ok, 右值在等号右边
a * b = 42; // 错误,右值在等号左边
[/ccb_c++]

在c++中,我们仍然可以用这个直观的办法来区分左值和右值。不过,c++中的用户自定义类型引入了关于可变性和可赋值性的微妙变化,这会让这个方法变的不那么地正确。我们没有必要继续深究下去,这里还有另外一种定义可以让你很好的处理关于右值的问题:左值是一个指向某内存空间的表达式,并且我们可以用&操作符获得该内存空间的地址。右值就是非左值的表达式。例如:

[ccb_c++]
// 左值:
//
int i = 42;
i = 43; // ok, i是左值
int* p = &i; // ok, i是左值
int& foo();
foo() = 42; // ok, foo()是左值
int* p1 = &foo(); // ok, foo()是左值

// 右值:
//
int foobar();
int j = 0;
j = foobar(); // ok, foobar()是右值
int* p2 = &foobar(); // 错误,不能取右值的地址
j = 42; // ok, 42是右值
[/ccb_c++]
如果你对左值和右值的严密的定义有兴趣的话,可以看下Mikael Kilpeläinen的文章[3]
继续阅读[译]详解C++右值引用

new的二三事

stackoverflow真是个绝佳的平台。基本上所有的问题都可以找到答案。今天遇到两个比较新鲜的关于new的问题,一个是operator new,另一个是placement new。

operator new

问个问题先:“new operator”和“operator new”有什么区别?new operator大家都很熟悉,就是平常用来创建对象的那个操作符咯。
[ccb_c++]
my_class *x = new my_class(0);
[/ccb_c++]
operator new是什么?好像从来没听过。operator new是用来分配内存的,仅此而已。在概念上来说,operator new和malloc类似。虽然operator new并不常用,但是如果要自己写容器之类的,那么operator new就有用武之地了。operator new可以这样用:
[ccb_c++]
char *x = static_cast(operator new(100));
[/ccb_c++]
operator new和new operator之间的区别在于,new operator会先用operator new去分配内存,然后调用类的构造函数去初始化对象。

placement new

上面说了,new的时候会先分配内存,然后调用构造函数。但是placement new允许在已经分配的内存上面直接构造对象。

这个东西是很有市场的。不用再次分配内存会节省很多时间,提高程序的效率。另外有时候我们不希望发生分配内存出错的情况,placement new就派上用场了,因为内存已经分配好了。placement new要这样用:
[ccb_c++]
char *buf = new char[sizeof(string)]; //pre-allocated buffer
string *p = new (buf) string(“hi”); //placement new
string *q = new string(“hi”); //ordinary heap allocation
[/ccb_c++]

参考

Difference between ‘new operator’ and ‘operator new’?
What uses are there for “placement new”?