您的位置:首页 > 编程语言 > C语言/C++

关于C++的右值、const引用、右值引用、const成员函数及相关扩展

2014-06-27 15:39 281 查看
函数式编程采用抽象的函数模型,将计算视为用函数对“值”作处理。“纯粹”的函数式编程避免涉及内存模型,禁止更改值及保存状态变量(这被称作“副作用”:side-effect)。一些语言如Haskell仍遵从这一规则。纯粹性使得计算易被充分优化,如改变执行顺序、并行执行、以及缓存函数计算结果等。
C/C++语言沿用了“值”的概念,称作“右值”。右值概念上没有内存地址,并且实际的内存分配可能被优化掉,如例中所示。

Complex c = Complex(1, 2)/*右值*/ + 3;
find_if(l.begin(), l.end(), bind2nd(less<int>(), 0)/*右值*/);
C/C++都禁止获取右值的地址。
const int *p = &3;                      /* 错 */
const Complex *q = &Complex(1, 2);      // 错
但C++允许获取右值的引用,并且遵照传统规则,对右值的引用仍需为const。
const int& n = 3;
Complex c1 = Complex(1, 2) + 3;         // 有多余的拷贝操作,可能被优化掉
const Complex& c2 = Complex(1, 2) + 3;  // 没有多余的拷贝操作
c1的例中,返回对象先被放到栈顶,再被拷贝到c1中;编译器可能根据优化规则传递c1地址,从而直接将返回对象构造在c1中。c2则直接使用栈顶的返回对象。因此,此处的const引用实际上是手动优化。编译器(或者用户代码)如何优化中间计算结果所占空间是个有趣的问题,例如:
Matrix m = m1 * m2 * m3 * m4;           // 返回时栈上有多个中间结果矩阵
一种解决方法是同时使用堆栈两头的空间。这种情况下,使用const引用的手动优化就不一定有好处了。
栈顶|m1*m2                  |堆顶
|m1*m2          m1*m2*m3|
|m1*m2*m3*m4    m1*m2*m3|
注意,C++应扩展支持大小在运行时才决定的类(参见ISO C99变长数组)。
cin << n << m;
Matrix<n, m> matrix;                    // 运行时选择矩阵大小。
引用在实现上与指针相似,但语义上有微妙的区别。指针可以重复赋值,引用则不能被更改;有指针常量(如int* const),而没有引用常量(如int& const)。类的const对象中的指针字段与引用字段也有不同的const语义。

struct A {
int& n;
int* p;
// ...
};
const A a(...);
a.n = 3;                                // 错,被引用的n自动成为const
// 我使用的g++-4.8编译器的实现有误!
a.p = q;                                // 错
*(a.p) = 3;                             // 指针本身为const,但可修改所指对象


由于在类中使用引用字段的情形很少,C++可为此语法作如下扩展:被引用的对象可被视为类的外部成员。这种表示方法适用于容器与递归数据结构,用来标明从属关系,并支持对数据整体的delete操作与const保护。(待讨论)

class IntList {
int n;
IntList& next;
};
IntList *p = ... ;
const IntList& l = *p;                  // 整个链表l不能被改动
delete p;                               // 自动删除整个链表
C++要求对右值的引用必须为const也并不合理,因为与函数式语言不同,右值一旦分配在内存中其状态就应当允许改动。这种问题在用到function对象时常出现,例如:
template<class Method> void processData(const Method& m) { ... }
processData(Moment<3>());               // 求3次矩
ofstream("myfile") << "some text";      // 快速地写一个文件的表达式
两个例子都因为要修改const对象而出错。前一个例子将参数m声明为(Method M)即可通过传递值而非引用来避免问题,多余的拷贝操作可被优化掉。后一个例子中的文件流则不能随便拷贝。

实际上,C++可以简单放宽限制,允许将右值赋给非const引用即可。C++11专门添加的右值引用(如int&&)似乎多余。

C++的另一个有关const的问题是要求为同一成员函数同时声明两个版本。实际上,const版本应能从非const版本自动生成。例如:
template<...> class vector {
public:
reference operator[](size_type n);
// 应能根据代码确定成员函数是否有const版本、及其返回类型。
const_reference operator[](size_type n) const;  // 应自动生成。
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息