代码之家  ›  专栏  ›  技术社区  ›  TheFogger

为什么C++中的引用不可恢复

  •  63
  • TheFogger  · 技术社区  · 15 年前

    C++引用有两个属性:

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

    指针是相反的:

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

    为什么C++中没有“不可空、可引用或指针”?我想不出一个很好的理由来解释为什么引用不能被重新研究。

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

    我不认为我曾经认为“伟大的,这个参考总是指同一个对象”。如果引用是可重设的,则仍可以获得如下当前行为:

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

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

    我这样重申我的问题: “参考文献背后的基本原理是什么? 物体的设计?为什么有推荐信被认为是有用的? 总是 是同一个对象,而不是仅当声明为const时?”

    干杯,菲利克斯

    17 回复  |  直到 8 年前
        1
  •  81
  •   Michael Burr    15 年前

    C++不允许你重新绑定引用的原因是在Stroustrup的“C++的设计和演化”中给出的:

    初始化之后,不可能更改引用所引用的内容。也就是说,一旦C++引用被初始化,就不能在以后引用另一个对象;它不能被重新绑定。我过去曾被algol68参考文献咬过 r1=r2 可以通过 r1 指向引用的对象或为其分配新的引用值 R1 (重新装订) R1 )取决于 r2 . 我想避免在C++中出现这样的问题。

        2
  •  32
  •   Geek    11 年前

    在C++中,常说“引用”。 对象“。在某种意义上,这是正确的:虽然在编译源代码时引用被当作指针处理,但引用的目的是表示在调用函数时不被复制的对象。由于引用不是可直接寻址的(例如,引用没有地址,返回对象的地址),因此在语义上重新分配它们是没有意义的。此外,C++已经有指针,它处理重新设置的语义。

        3
  •  17
  •   Stack Overflow is garbage    15 年前

    因为这样就没有不可以为0的可重新密封类型。除非,您包含3种类型的引用/指针。这只会使语言复杂化,只会获得很少的收益(然后为什么不添加第四种类型呢?不可重新密封的引用,可以是0?)

    一个更好的问题可能是,为什么您希望引用可重新密封?如果是的话,这会使它们在很多情况下变得不那么有用。这将使编译器更难进行别名分析。

    Java或C语言中引用的主要原因似乎是因为它们执行指针的工作。它们指向物体。它们不是对象的别名。

    下面的效果应该是什么?

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

    在C++中,使用不可重新引用的引用是很简单的。j是i的别名,最后得到值43。

    如果引用是可重设的,那么第三行将引用j绑定到另一个值。它不再是I的别名,而是整型文字43(当然,这是无效的)。或者一个更简单(至少在语法上有效)的例子:

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

    具有可重新密封的引用。在评估完这段代码后,j会指向k。 用C++的不可恢复引用,J仍然指向I,并且我被赋值为43。

    使引用可重新研究会改变语言的语义。引用不能再是另一个变量的别名。相反,它变成了一个单独的值类型,具有自己的赋值运算符。那么参考文献的最常见用法之一就不可能了。没有什么可以换来的。新获得的引用功能已经以指针的形式存在。所以现在我们有两种方法来做同样的事情,没有办法去做当前C++语言中的引用。

        4
  •  5
  •   Brian R. Bondy    15 年前

    引用不是指针,它可以在后台实现为指针,但其核心概念并不等同于指针。应该像这样看一个参考文献 *is* 它所指的对象。因此,不能更改它,也不能为空。

    指针只是一个保存内存地址的变量。 指针本身有一个自己的内存地址,在该内存地址中它保存着另一个内存地址。 据说是指向。 引用不相同,它没有自己的地址,因此不能更改为“保留”另一个地址。

    我认为 parashift C++ FAQ on references 说得最好:

    重要提示:即使 引用通常使用 基础程序集中的地址 语言,请不要想一个 作为一个有趣的指针引用 对一个物体。参考文献是 对象。它不是指向 对象,也不是对象的副本。它 是对象。

    又在 FAQ 8.5 :

    与指针不同,一旦引用 绑定到对象,它不能 “重新放置”到另一个对象。这个 引用本身不是一个对象(它 没有身份;取 一份推荐信给你地址 参考文献;记住:参考文献 是它的参照物)。

        5
  •  5
  •   j_random_hacker    15 年前

    可重设引用在功能上与指针相同。

    关于可空性:您不能保证这样的“可重新密封引用”在编译时是非空的,因此任何这样的测试都必须在运行时进行。您可以通过编写智能指针样式的类模板来实现这一点,该模板在初始化或分配空值时引发异常:

    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* .

        6
  •  4
  •   snemarch    15 年前

    它可能不会混淆C++引用“别名”?正如其他人提到的,C++中的引用应该是 作为 变量 它们指的不是指针/ 参考 到变量。因此,我想不出一个很好的理由 应该 可重新安置。

    处理指针时,通常允许将空值作为值(否则,您可能需要一个引用)。如果您特别希望不允许保留空值,则可以始终为自己的智能指针类型编写代码;)

        7
  •  3
  •   dhaumann    9 年前

    最重要的是,这里的许多答案都有点模糊,甚至有些离题(例如,这不是因为引用不能为零或类似,事实上,您可以轻松地构造一个引用为零的示例)。

    重新设置引用不可能的真正原因很简单。

    • 指针允许您做两件事:更改指针后面的值(通过 -> * 和更改指针本身(直接分配 = )例子:

      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);
    }
    

    希望能有所帮助:—)

        8
  •  2
  •   Mr Fooz    15 年前

    C++引用可以 有时被迫 使用一些编译器 (这样做只是个坏主意*,违反了标准*)。

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

    编辑: 根据许多比我更了解标准的人所说,上面的代码产生了“未定义的行为”。在GCC和Visual Studio的某些版本中,我看到了这样做的预期效果:相当于将指针设置为空(并在访问时导致空指针异常)。

        9
  •  1
  •   John Dibling    15 年前

    你不能这样做:

    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 );
    
        10
  •  1
  •   Earth Engine    11 年前

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

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

    以JALF为例

    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
    }
    
        11
  •  0
  •   dmckee --- ex-moderator kitten    15 年前

    我可以想象它与优化有关。

    静态优化是 许多的 当你清楚地知道一个变量的内存位意味着什么时,就更容易了。指针会破坏此条件,并且可重新设置的引用也会破坏此条件。

        12
  •  0
  •   tpdi    15 年前

    因为有时候事情不应该是有意义的。(例如,对单件的引用。)

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

    但大多数情况下,因为它允许使用真正是指针的对象,但其作用类似于本地值对象。C++试图引用Stroustrup,使类实例“做为int”。通过vaue传递int是很便宜的,因为int适合于机器寄存器。类通常比int大,按值传递它们有很大的开销。

    如果能够传递一个“像”值对象的指针(通常是一个int的大小,或者可能是两个int),那么我们就可以编写更干净的代码,而不需要取消引用的“实现细节”。并且,与运算符重载一起,它允许我们编写类,使用与ints使用的语法类似的语法。特别是,它允许我们用语法编写模板类,这些语法同样适用于原语(如int)和类(如复数类)。

    而且,特别是在运算符重载的情况下,有些地方我们应该返回一个对象,但同样,返回指针要便宜得多。再次重申,返回引用是我们的“out”。

    指点很难。不适合你,也许,也不适合任何一个意识到指针只是内存地址的值的人。但回想我的CS101课,他们绊倒了一些学生。

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

    可能会令人困惑。

    见鬼,在经历了40年的“C”之后,人们仍然不能同意指针声明是否应该是:

    char* p;
    

    char *p;
    
        13
  •  0
  •   Joshua    15 年前

    我一直想知道为什么他们没有为此做一个引用分配操作符(比如:=)。

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

    不,我不建议重复我的技巧。如果移植到一个完全不同的体系结构中,它将崩溃。

        14
  •  0
  •   anon    15 年前

    半认真:imho使它们与指针稍有不同;)你知道你可以写:

    MyClass & c = *new MyClass();
    

    如果你以后也能写:

    c = *new MyClass("other")
    

    在指针旁边加上任何引用是否有意义?

    MyClass * a =  new MyClass();
    MyClass & b = *new MyClass();
    a =  new MyClass("other");
    b = *new MyClass("another");
    
        15
  •  0
  •   hasen    15 年前

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

        16
  •  0
  •   Community c0D3l0g1c    7 年前

    我同意接受的回答。 但是对于警察来说,他们的行为很像指针。

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

    作品。 见

    Design reasons for the behavior of reference members of classes passed by const reference

        17
  •  0
  •   lorro    8 年前

    如果您想要一个引用的成员变量,并且希望能够重新绑定它,那么有一个解决方法。虽然我发现它有用且可靠,但是请注意,它在内存布局上使用了一些(非常弱的)假设。这取决于你是否在你的编码标准之内。

    #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;
    }
    

    这不是很有效,因为您需要为每个可重新分配的引用字段使用一个单独的类型,并使它们成为基类;此外,这里还存在一个弱假设,即具有单个引用类型的类不会具有 __vfptr 或其他 type_id -可能破坏MyType运行时绑定的相关字段。我认识的所有编译器都满足这个条件(不这样做毫无意义)。

    推荐文章