为什么参考不能在C ++中重置

C ++引用有两个属性:

  • 他们总是指向同一个对象。
  • 他们不能是0。

指针是相反的:

  • 他们可以指向不同的对象。
  • 他们可以是0。

为什么在C ++中没有“不可空,可复位的引用或指针”? 我想不出一个很好的理由,为什么不应该重新提供参考。

编辑:问题经常出现,因为我通常使用引用时,我想确保一个“关联”(我在这里避免单词“参考”或“指针”)永远无效。

我不认为我曾经想过“这个参考文献总是指向同一个客体”。 如果引用是可复位的,那么仍然可以得到如下的当前行为:

int i = 3; int& const j = i; 

这已经是合法的C ++,但毫无意义。

我重申我的问题是这样的: “引用背后的原理什么?对象的devise是什么?为什么引用总是相同的对象,而不是只有当声明为const?

干杯,菲利克斯

Stroustrup的“C ++的devise和演变”给出了C ++不允许重新引用引用的原因:

在初始化之后不可能改变引用所引用的内容。 也就是说,一旦C ++引用被初始化,以后就不能引用不同的对象; 它不能被重新绑定。 我曾经被Algol68引用咬过,其中r1=r2可以通过r1分配给所引用的对象,或者根据r2的types为r1分配一个新的引用值(重新绑定r1 )。 我想在C ++中避免这样的问题。

在C ++中,人们常说“参照就是对象”。 在某种意义上,这是事实:尽pipe在编译源代码时将引用作为指针处理,但引用旨在表示在调用函数时不会被复制的对象。 由于引用不是直接可寻址的(例如,引用没有地址,并且返回对象的地址),所以重新分配它们在语义上是不合理的。 此外,C ++已经有指针,它处理重新设置的语义。

因为那么你就没有可重复的types,不能是0.除非你包含3种types的引用/指针。 这只会使语言复杂化很less(为什么不添加第四种types?不可重置的参考可以是0?)

一个更好的问题可能是,你为什么要引用是可重置的? 如果是这样的话,那么在很多情况下它们就不那么有用了。 这将使编译器难以进行别名分析。

看来Java或C#中引​​用的主要原因是可重复的,因为他们做指针的工作。 他们指向对象。 它们不是对象的别名。

以下的效应是什么?

 int i = 42; int& j = i; j = 43; 

在今天的C ++中,不可重新引用的引用很简单。 j是我的别名,我以43结束。

如果引用是可复位的,那么第三行会将引用j绑定到不同的值。 它不再是别名我,而是整数字面量43(当然是无效的)。 或者可能是一个简单的(或者至less是语法上有效的)例子:

 int i = 42; int k = 43; int& j = i; j = k; 

与可复位的参考。 j在评估这个代码之后会指向k。 用C ++的不可重新引用,j仍然指向我,我被赋值为43。

使引用可重新定位会改变语言的语义。 该引用不能再作为另一个variables的别名。 相反,它会成为一个独立的值types,并带有自己的赋值运算符。 然后,最常见的用法之一是不可能的。 而且没有任何交换。 新获得的引用function已经以指针的forms存在。 所以现在我们有两种方法来做同样的事情,而且没有办法去做当前C ++语言中的引用。

可复位的引用在function上与指针相同。

关于可空性:在编译时你不能保证这样的“可重新引用”是非空的,所以任何这样的testing都必须在运行时进行。 你可以通过编写一个智能的指针式的类模板来实现这一点,当初始化或分配NULL时抛出exception:

 struct null_pointer_exception { ... }; template<typename T> struct non_null_pointer { // No default ctor as it could only sensibly produce a NULL pointer non_null_pointer(T* p) : _p(p) { die_if_null(); } non_null_pointer(non_null_pointer const& nnp) : _p(nnp._p) {} non_null_pointer& operator=(T* p) { _p = p; die_if_null(); } non_null_pointer& operator=(non_null_pointer const& nnp) { _p = nnp._p; } T& operator*() { return *_p; } T const& operator*() const { return *_p; } T* operator->() { return _p; } // Allow implicit conversion to T* for convenience operator T*() const { return _p; } // You also need to implement operators for +, -, +=, -=, ++, -- private: T* _p; void die_if_null() const { if (!_p) { throw null_pointer_exception(); } } }; 

这可能是有用的 – 一个采用non_null_pointer<int>参数的函数肯定会向调用方传递更多的信息,而不是一个采用int*的函数。

将C ++引用命名为“别名”可能不太容易引起混淆? 正如其他人所提到的,C ++中的引用应该尽可能地作为它们引用的variables ,而不是作为variables的指针/ 引用 。 因此,我想不出有什么好的理由可以重置。

当处理指针时,通常允许null作为一个值(否则,你可能需要一个引用)。 如果你特别想禁止持有null,你总是可以编写自己的智能指针types;)

一个引用不是一个指针,它可能作为一个指针在后台实现,但其核心概念并不等同于一个指针。 参考应该被看作*is*它所指的对象。 因此你不能改变它,它不能是NULL。

指针只是一个保存内存地址的variables。 指针本身有一个自己的内存地址,并且在该内存地址中它保存着被指向的另一个内存地址引用是不一样的,它没有它自己的地址,因此它不能被改为“保存”另一个地址。

我认为参考文献中的Parashift C ++ FAQ说得最好:

重要说明:尽pipe通常使用底层汇编语言中的地址来实现引用,但请不要将引用看作指向对象的有趣的指针。 参考是对象。 它不是指向对象的指针,也不是对象的副本。 这是对象。

并再次在FAQ 8.5中 :

与指针不同的是,一旦引用被绑定到一个对象,它就不能被“重置”到另一个对象。 引用本身不是一个对象(它没有身份;引用的地址给出了引用的地址;记住:引用是它的引用)。

有些编译器 有时候会把 C ++引用强制为 0(这样做只是一个坏主意,违反了标准*)。

 int &x = *((int*)0); // Illegal but some compilers accept it 

编辑:根据知道标准比我自己好得多的人,上面的代码产生“未定义的行为”。 在GCC和Visual Studio的至less一些版本中,我已经看到了这样做的预期的事情:相当于将指针设置为NULL(并在访问时导致NULL指针exception)。

令人吃惊的是,这里的许多答案有点模糊或甚至在旁边(例如,这不是因为参考不能是零或类似的,事实上,你可以很容易地构build一个参考为零的例子)。

为什么重新设定参考是不可能的真正原因是相当简单的。

  • 指针使您能够做两件事情:改变指针后面的值(通过->*运算符),并改变指针本身(直接赋值= )。 例:

     int a; int * p = &a; 
    1. 更改值需要解引用: *p = 42;
    2. 改变指针: p = 0;
  • 引用允许您只更改值。 为什么? 由于没有其他语法来表示重新设置。 例:

     int a = 10; int b = 20; int & r = a; r = b; // re-set r to b, or set a to 20? 

换句话说,如果你被允许重新设置一个参考,这将是不明确的。 通过引用传递更有意义:

 void foo(int & r) { int b = 20; r = b; // re-set r to a? or set a to 20? } void main() { int a = 10; foo(a); } 

希望有帮助:-)

这实际上不是一个答案,而是解决此限制的一个解决方法。

基本上,当你试图“重新绑定”一个引用时,你实际上试图使用相同的名字在下面的上下文中引用一个新的值。 在C ++中,这可以通过引入块范围来实现。

以贾尔夫的例子

 int i = 42; int k = 43; int& j = i; //change i, or change j? j = k; 

如果你想改变我,写如上。 但是,如果要将j的含义更改为k ,则可以这样做:

 int i = 42; int k = 43; int& j = i; //change i, or change j? //change j! { int& j = k; //do what ever with j's new meaning } 

我会想象它与优化有关。

当你明确地知道variables意味着什么位的内存时,静态优化就容易得多 。 指针打破了这个条件,并且可重新引用的参考也是如此。

因为有时候事情不应该是可以重新定位的。 (例如,对单例的引用。)

因为在函数中知道你的参数不能为空是很好的。

但大多数情况下,因为它允许使用具有真正的指针的东西,但是它像一个本地值对象那样工作。 C ++努力引用Stroustrup,使类实例“做为整数d”。 通过vaue传递一个int是便宜的,因为一个int适合一个机器寄存器。 类通常比整数大,并且通过值来传递它们具有相当大的开销。

能够传递一个“看起来像”一个值对象的指针(通常是一个int的大小,或者两个整数)允许我们编写更干净的代码,而不需要提供“解除引用的实现细节”。 而且,与运算符重载一起,它允许我们编写类使用类似于ints使用的语法的语法。 特别是,它允许我们编写模板类,其语法可以同样适用于基本types,例如整数和类(如复数类)。

而且,尤其是在运算符重载的情况下,有些地方我们应该返回一个对象,但是再次返回一个指针要便宜得多。 再次提醒,返回参考是我们的“出。

指针很难。 也许不是为了你,而不是任何人意识到指针只是内存地址的值。 但回想起我的CS 101课程,他们绊倒了一些学生。

 char* p = s; *p = *s; *p++ = *s++; i = ++*p; 

可以混淆。

哎,经过40年的C,人们还是不能同意指针声明应该是:

 char* p; 

要么

 char *p; 

你不能这样做:

 int theInt = 0; int& refToTheInt = theInt; int otherInt = 42; refToTheInt = otherInt; 

…为什么secondInt和firstInt在这里没有相同的值:

 int firstInt = 1; int secondInt = 2; secondInt = firstInt; firstInt = 3; assert( firstInt != secondInt ); 

我总是想知道为什么他们没有做一个参考赋值操作符(比如说=)。

为了让人们紧张,我写了一些代码来改变结构中的引用的目标。

不,我不build议重复我的伎俩。 如果移植到一个完全不同的架构,它将会被打破。

半认真的:恕我直言,使他们有点不同于指针;)你知道你可以写:

 MyClass & c = *new MyClass(); 

如果你以后也可以写:

 c = *new MyClass("other") 

与指针一起引用是否有意义?

 MyClass * a = new MyClass(); MyClass & b = *new MyClass(); a = new MyClass("other"); b = *new MyClass("another"); 

C ++中的引用不可空的事实是它们只是一个别名的副作用。

我同意接受的答案。 但是对于常态来说,他们的performance很像指针。

 struct A{ int y; int& x; A():y(0),x(y){} }; int main(){ A a; const A& ar=a; ar.x++; } 

作品。 看到

由const引用传递的类的引用成员行为的devise原因

有一个解决方法,如果你想成为一个引用的成员variables,你想能够重新绑定它。 虽然我发现它很有用也很可靠,但请注意,它在内存布局上使用了一些(非常弱)的假设。 这取决于你的编码标准。

 #include <iostream> struct Field_a_t { int& a_; Field_a_t(int& a) : a_(a) {} Field_a_t& operator=(int& a) { // a_.~int(); // do this if you have a non-trivial destructor new(this)Field_a_t(a); } }; struct MyType : Field_a_t { char c_; MyType(int& a, char c) : Field_a_t(a) , c_(c) {} }; int main() { int i = 1; int j = 2; MyType x(i, 'x'); std::cout << x.a_; x.a_ = 3; std::cout << i; ((Field_a_t&)x) = j; std::cout << x.a_; x.a_ = 4; std::cout << j; } 

这不是非常有效,因为每个可重新分配的引用字段都需要一个单独的types,并使它们成为基类; 此外,这里有一个弱的假设,一个具有单一引用types的类将不会有一个__vfptr或任何其他可能会破坏MyType的运行时绑定的type_id相关字段。 我所知道的所有编译器都满足这个条件(这样做没有意义)。