我们公司有这个一段代码,后来看了高效c++之后就总感觉有问题。不试不知道,一试全是地雷阵。
一般会写这么一个函数,返回的是引用。这样也符合C++里面的一些思想。但如果这样的函数没有用好的话,留下的就是一个地雷
std::vector<int>& Return_null_reference()
{
.....
}
例如有如下代码
std::vector<int>& Return_null_reference()
{
std::vector<int> *pTmp = NULL;
return *pTmp;
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
std::vector<int> *pTmp1 = NULL;
pTmp1 = &Return_null_reference();
std::vector<int> &pTmp2 = Return_null_reference();
Sleep(1);
}
我在vs 2008 sp1中编译不但能够通过,而且不会报错。执行的结果就是,ptmp1指向了一个空指针,ptmp2引用了一个空指针。
这里实际上看上去ptmp2会有问题,因为在引用的变量定义时,需要在编译器就要确定引用的对象是一个非NULL地址。但事实上这里因为引用了一个函数的返回,由于函数的返回值是属于运行时问题,所以编译器不做检查。于是就留下了一个坑。
高效C++中说过,不要随便引用一个指针指向的对象,因为有可能那个指针是指向NULL。
同时也隐约讲过引用不的不当反而比指针会更危险。
要利用编译器检查引用非NULL的特性,需要显示的指明被引用的对象是编译时就可以确定内存地址的。
class A
{
public:
…
}
一般对于使用者来说这个类只有1个数据结构,那就是类本身。
实际上,一个类包含了3个数据结构。
除了类本身以外,还包含了一个函数表、和指向函数表的指针。
函数表是用来保存虚函数在本类中的中的具体实现地址的。
而指向函数表的指针,值用于表示某个虚函数在函数表的地址。
之前简单看过一点c++11的特性,里面讲了一堆左值和右值。说来说去有一部分还是在谈论关于临时变量的问题。
目前有这么一个函数:
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
Rational result(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
return result;
}
上面这个函数在调用的时候因为返回的是局部变量,因此返回的时候编译器会插入代码会产生一个临时变量,然后将result拷贝到临时变量中返回出去。但让这里谈论的前提是,一些编译器的优化关掉。
而对于下面的代码来说编译器的行为就会不一样。因为这段代码已经显式的告诉编译器,这个这里会构建一个匿名的临时变量并返回,因此编译器在这里不会再次构建临时变量,而是直接将代码中创建的匿名对象直接返回出去给外面。这样做可以节约一个对象的创建和销毁时间复杂度。
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
在调用函数时,一般都不建议采用值传递,原因是因为要建立一个临时的拷贝;这样会带来空间和时间复杂度的提升。
一般除非很特殊情况,很少把对象以值的方式进行传递到函数里面。
实际上,不讲对象做值传递还有一个比较重要的原因。
在C++的多态中,如果父类的指针指向了一个子类对象,并对该对象做值传递,会导致子类数据丢失。大致代码如下:
class CA
{
};
class CB : public CA
{
};
void func(CA src);
int main()
{
CA* p1 = new CB;
func(*p1);
....
}
在这个代码里,实际上调用func时,使用了CA的默认拷贝构造函数,因为对于CB的所有数据就丢失了,在func里面的临时对象也仅仅是CA类型。
首先说一下++重载符,++分为前后两种方式的调用。因此就有了两种的符号的调用。大致如下(对于后置++的做法采用了不严谨的重载,返回的应该是 const对象)
namespace class_cplusplus10
{
class CBase
{
public:
CBase& operator++() //这里是前置++调用
{
printf(_T("class_cplusplus10::CBase& operator++()\n"));
return *this;
}
CBase& operator++(int) //这里是后置++调用
{
printf(_T("class_cplusplus10::CBase& operator++(int)\n"));
return *this;
}
CBase& operator--()
{
printf(_T("class_cplusplus10::CBase& operator--()\n"));
return *this;
}
CBase& operator--(int)
{
printf(_T("class_cplusplus10::CBase& operator--(int)\n"));
return *this;
}
};
}
上面的代码已经很好的说明了哪一些是前置调用重载,哪一些是后置调用的重载。
然后通过这些函数可以来看看一些哗众取宠的笔试题。
class_cplusplus10::CBase base;
++base++;
问++base++的调用是怎样的,通过调试发现,实际上后置++是先被调用,然后前置++。
不过这里是c++,而这种笔试题往往考的是操作符的优先级。也许一个对象和一个内置类型的变量存在一些不同
那个过chromium上的source tarball解压以后,在执行了gclient sync之后,在电脑A上面编译chrome工程ok。
然后执行clean,删除 build/debug下的所有文件,打压缩包。
保存到另一个电脑B上时,出现一大堆编译问题。
chromium的代码与编译脚本的生成太复杂,以至于有时候很难搞清楚到底要保留哪一些,要剔除哪一些。
现在想独立把chromium的代码拿下来然后做一个分支,发现太困难了。都搞了整个十一了,还没弄好,无奈至极!打算放弃!sh!t