简体   繁体   English

为什么引用不能在 C++ 中重新安装

[英]Why are references not reseatable in C++

C++ references have two properties: C++ 引用有两个属性:

  • They always point to the same object.它们总是指向同一个对象。
  • They can not be 0.它们不能为 0。

Pointers are the opposite:指针正好相反:

  • They can point to different objects.它们可以指向不同的对象。
  • They can be 0.它们可以是 0。

Why is there no "non-nullable, reseatable reference or pointer" in C++?为什么在 C++ 中没有“不可为空、可重置的引用或指针”? I can't think of a good reason why references shouldn't be reseatable.我想不出一个很好的理由为什么引用不应该被重新安装。

Edit: The question comes up often because I usually use references when I want to make sure that an "association" (I'm avoiding the words "reference" or "pointer" here) is never invalid.编辑:这个问题经常出现,因为当我想确保“关联”(我在这里避免使用“引用”或“指针”这个词)永远不会无效时,我通常会使用引用。

I don't think I ever thought "great that this ref always refers to the same object".我认为我从来没有想过“这个 ref 总是指同一个对象,这很棒”。 If references were reseatable, one could still get the current behavior like this:如果引用是可重新安装的,人们仍然可以获得这样的当前行为:

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

This is already legal C++, but meaningless.这已经是合法的 C++,但毫无意义。

I restate my question like this: "What was the rationale behind the 'a reference is the object' design? Why was it considered useful to have references always be the same object, instead of only when declared as const?"我这样重申我的问题: “'引用就是对象'设计背后的基本原理什么?为什么引用总是同一个对象而不是仅在声明为 const 时被认为是有用的?”

Cheers, Felix干杯,菲利克斯

The reason that C++ does not allow you to rebind references is given in Stroustrup's "Design and Evolution of C++" : C++ 不允许您重新绑定引用的原因在 Stroustrup 的“C++ 的设计和演化”中给出:

It is not possible to change what a reference refers to after initialization.初始化后无法更改引用所指的内容。 That is, once a C++ reference is initialized it cannot be made to refer to a different object later;也就是说,一旦 C++ 引用被初始化,以后就不能再引用不同的对象; it cannot be re-bound.它不能重新绑定。 I had in the past been bitten by Algol68 references where r1=r2 can either assign through r1 to the object referred to or assign a new reference value to r1 (re-binding r1 ) depending on the type of r2 .我过去曾被 Algol68 引用所困扰,其中r1=r2可以通过r1分配给引用的对象,也可以根据r2的类型为r1 (重新绑定r1 )分配一个新的引用值。 I wanted to avoid such problems in C++.我想在 C++ 中避免这样的问题。

In C++, it is often said that "the reference is the object".在 C++ 中,常说“引用对象”。 In one sense, it is true: though references are handled as pointers when the source code is compiled, the reference is intended to signify an object that is not copied when a function is called.从某种意义上说,这是正确的:尽管在编译源代码时引用作为指针处理,但引用旨在表示在调用函数时未复制的对象。 Since references are not directly addressable (for example, references have no address, & returns the address of the object), it would not semantically make sense to reassign them.由于引用不能直接寻址(例如,引用没有地址,& 返回对象的地址),重新分配它们在语义上没有意义。 Moreover, C++ already has pointers, which handles the semantics of re-setting.此外,C++ 已经有了处理重新设置语义的指针。

Because then you'd have no reseatable type which can not be 0. Unless, you included 3 types of references/pointers.因为那样你就没有不能为 0 的可重置类型。除非你包含了 3 种类型的引用/指针。 Which would just complicate the language for very little gain (And then why not add the 4th type too? Non-reseatable reference which can be 0?)这只会使语言复杂化以获得很少的收益(然后为什么不也添加第 4 种类型?不可重新安装的引用,它可以是 0?)

A better question may be, why would you want references to be reseatable?一个更好的问题可能是,为什么您希望引用可重新安装? If they were, that would make them less useful in a lot of situations.如果是这样,那将使它们在很多情况下变得不那么有用。 It would make it harder for the compiler to do alias analysis.这会使编译器更难进行别名分析。

It seems that the main reason references in Java or C# are reseatable is because they do the work of pointers.似乎 Java 或 C# 中的引用可重新安装的主要原因是因为它们完成了指针的工作。 They point to objects.它们指向对象。 They are not aliases for an object.它们不是对象的别名。

What should the effect of the following be?下面的效果应该是什么?

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

In C++ today, with non-reseatable references, it is simple.在今天的 C++ 中,使用不可重新安装的引用,这很简单。 j is an alias for i, and i ends up with the value 43. j 是 i 的别名,而 i 的值是 43。

If references had been reseatable, then the third line would bind the reference j to a different value.如果引用是可重置的,那么第三行会将引用 j 绑定到不同的值。 It would no longer alias i, but instead the integer literal 43 (which isn't valid, of course).它不再是 i 的别名,而是整数文字 43(当然,这是无效的)。 Or perhaps a simpler (or at least syntactically valid) example:或者也许是一个更简单(或至少在语法上有效)的示例:

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

With reseatable references.带有可重新安装的参考。 j would point to k after evaluating this code. j 将在评估此代码后指向 k。 With C++'s non-reseatable references, j still points to i, and i is assigned the value 43.对于 C++ 的不可重置引用,j 仍然指向 i,而 i 被赋值为 43。

Making references reseatable changes the semantics of the language.使引用可重坐会改变语言的语义。 The reference can no longer be an alias for another variable.引用不能再是另一个变量的别名。 Instead it becomes a separate type of value, with its own assignment operator.相反,它变成了一种单独的值类型,具有自己的赋值运算符。 And then one of the most common usages of references would be impossible.然后引用的最常见用法之一将是不可能的。 And nothing would be gained in exchange.并且不会得到任何交换。 The newly gained functionality for references already existed in the form of pointers.新获得的引用功能已经以指针的形式存在。 So now we'd have two ways to do the same thing, and no way to do what references in the current C++ language do.所以现在我们有两种方法可以做同样的事情,而无法做当前 C++ 语言中的引用所做的事情。

Intrestingly, many answers here are a bit fuzzy or even beside the point (eg it's not because references cannot be zero or similar, in fact, you can easily construct an example where a reference is zero).有趣的是,这里的许多答案都有些模糊甚至离题(例如,这不是因为引用不能为零或相似,事实上,您可以轻松构建一个引用为零的示例)。

The real reason why re-setting a reference is not possible is rather simple.无法重新设置参考的真正原因相当简单。

  • Pointers enable you to do two things: To change the value behind the pointer (either through the -> or the * operator), and to change the pointer itself (direct assign = ).指针使您能够做两件事:更改指针后面的值(通过->*运算符),以及更改指针本身(直接赋值= )。 Example:例子:

     int a; int * p = &a;
    1. Changing the value requires dereferencing: *p = 42;更改值需要取消引用: *p = 42;
    2. Changing the pointer: p = 0;改变指针: p = 0;
  • References allow you to only change the value.引用允许您只更改值。 Why?为什么? Since there is no other syntax to express the re-set.由于没有其他语法来表达重新设置。 Example:例子:

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

In other words, it would be ambiguous if you were allowed to re-set a reference.换句话说,如果您被允许重新设置引用,那将是模棱两可的。 It makes even more sense when passing by reference:通过引用传递时更有意义:

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

Hope that helps :-)希望有帮助:-)

A reference is not a pointer, it may be implemented as a pointer in the background, but its core concept is not equivalent to a pointer.引用不是指针,它可能在后台实现为指针,但其核心概念并不等同于指针。 A reference should be looked at like it *is* the object it is referring to.一个引用应该被视为它*is*它所指的对象。 Therefore you cannot change it, and it cannot be NULL.因此你不能改变它,它不能为 NULL。

A pointer is simply a variable that holds a memory address.指针只是一个保存内存地址的变量。 The pointer itself has a memory address of its own, and inside that memory address it holds another memory address that it is said to point to.指针本身有一个自己的内存地址,在该内存地址中,它保存着另一个据说指向的内存地址 A reference is not the same, it does not have an address of its own, and hence it cannot be changed to "hold" another address.引用不一样,它没有自己的地址,因此不能更改为“持有”另一个地址。

I think the parashift C++ FAQ on references says it best:我认为参考资料上parashift C++ FAQ说得最好:

Important note: Even though a reference is often implemented using an address in the underlying assembly language, please do not think of a reference as a funny looking pointer to an object.重要提示:尽管引用通常是使用底层汇编语言中的地址实现的,但请不要将引用视为指向对象的有趣指针。 A reference is the object.引用是对象。 It is not a pointer to the object, nor a copy of the object.它不是指向对象的指针,也不是对象的副本。 It is the object.它是对象。

and again in FAQ 8.5 :再次在FAQ 8.5 中

Unlike a pointer, once a reference is bound to an object, it can not be "reseated" to another object.与指针不同,一旦引用绑定到一个对象,它就不能“重新定位”到另一个对象。 The reference itself isn't an object (it has no identity; taking the address of a reference gives you the address of the referent; remember: the reference is its referent).引用本身不是一个对象(它没有身份;获取引用的地址会给你所指的地址;记住:引用是它的所指)。

A reseatable reference would be functionally identical to a pointer.可重置引用在功能上与指针相同。

Concerning nullability: you cannot guarantee that such a "reseatable reference" is non-NULL at compile time, so any such test would have to take place at runtime.关于可空性:您不能保证这样的“可重坐引用”在编译时是非空的,因此任何此类测试都必须在运行时进行。 You could achieve this yourself by writing a smart pointer-style class template that throws an exception when initialised or assigned NULL:您可以通过编写一个智能指针样式的类模板来实现这一点,该模板在初始化或分配为 NULL 时抛出异常:

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

This might be useful on occasion -- a function taking a non_null_pointer<int> parameter certainly communicates more information to the caller than does a function taking int* .这有时可能很有用 - 使用non_null_pointer<int>参数的函数肯定比使用int*的函数向调用者传达更多信息。

It would probably have been less confusing to name C++ references "aliases"?将 C++ 引用命名为“别名”可能不会那么混乱? As others have mentioned, references in C++ should be though of as the variable they refer to, not as a pointer/ reference to the variable.正如其他人所提到的,C++ 中的引用应该被视为它们所引用的变量,而不是变量的指针/引用 As such, I can't think of a good reason they should be resettable.因此,我不能想到一个很好的理由,他们应该是复位。

when dealing with pointers, it often makes sense allowing null as a value (and otherwise, you probably want a reference instead).在处理指针时,允许 null 作为值通常是有意义的(否则,您可能需要一个引用)。 If you specifically want to disallow holding null, you could always code your own smart pointer type ;)如果您特别想禁止持有空值,您可以随时编写自己的智能指针类型;)

C++ references can sometimes be forced to be 0 with some compilers (it's just a bad idea to do so*, and it violates the standard*).某些编译器有时会强制C++ 引用0(这样做是个坏主意*,而且违反了标准*)。

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

EDIT: according to various people who know the standard much better than myself, the above code produces "undefined behavior".编辑:根据比我更了解标准的各种人,上面的代码产生了“未定义的行为”。 In at least some versions of GCC and Visual Studio, I've seen this do the expected thing: the equivalent of setting a pointer to NULL (and causes a NULL pointer exception when accessed).至少在 GCC 和 Visual Studio 的某些版本中,我已经看到这做了预期的事情:相当于将指针设置为 NULL(并在访问时导致 NULL 指针异常)。

This is not actually an answer, but a workaround for this limitation.这实际上不是答案,而是针对此限制的解决方法。

Basically, when you try to "rebind" a reference you are actually trying to use the same name to refer to a new value in the following context.基本上,当您尝试“重新绑定”一个引用时,您实际上是在尝试使用相同的名称来引用以下上下文中的新值。 In C++, this can be achieve by introducing a block scope.在 C++ 中,这可以通过引入块作用域来实现。

In jalf's example在jalf的例子中

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

if you want to change i, write it as above.如果你想改变i,就按上面写。 However, if you want to change the meaning of j to mean k , you can do this:但是,如果您想将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
}

You can't do this:你不能这样做:

int theInt = 0;
int& refToTheInt = theInt;

int otherInt = 42;
refToTheInt = otherInt;

...for the same reason why secondInt and firstInt don't have the same value here: ...出于同样的原因,为什么 secondInt 和 firstInt 在这里没有相同的值:

int firstInt = 1;
int secondInt = 2;
secondInt = firstInt;
firstInt = 3;

assert( firstInt != secondInt );

I agree with the accepted answer.我同意接受的答案。 But for constness, they behave much like pointers though.但是对于常量来说,它们的行为很像指针。

struct A{
    int y;
    int& x;
     A():y(0),x(y){}
};

int main(){
  A a;
  const A& ar=a;
  ar.x++;
}

works.作品。 See

Design reasons for the behavior of reference members of classes passed by const reference const 引用传递的类的引用成员行为的设计原因

There's a workaround if you want a member variable that's a reference and you want to be able to rebind it.如果您想要一个作为引用的成员变量并且您希望能够重新绑定它,则有一种解决方法。 While I find it useful and reliable, note that it uses some (very weak) assumptions on memory layout.虽然我发现它有用且可靠,但请注意它对内存布局使用了一些(非常弱的)假设。 It's up to you to decide whether it's within your coding standards.由您决定它是否符合您的编码标准。

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

This is not very efficient as you need a separate type for each reassignable reference field and make them base classes;这不是很有效,因为您需要为每个可重新分配的引用字段使用单独的类型并使它们成为基类; also, there's a weak assumption here that a class having a single reference type won't have a __vfptr or any other type_id -related field that could potentially destroy runtime bindings of MyType.此外,这里有一个弱假设,即具有单个引用类型的类不会有__vfptr或任何其他type_id相关字段,这些字段可能会破坏 MyType 的运行时绑定。 All the compilers I know satisfy that condition (and it would make little sense not doing so).我知道的所有编译器都满足该条件(不这样做毫无意义)。

I would imagine that it is related to optimization.我想这与优化有关。

Static optimization is much easier when you can know unambiguously what bit of memory a variable means.当您可以明确地知道变量意味着什么内存位时,静态优化会容易得多 Pointers break this condition and re-setable reference would too.指针会破坏这种情况,可重新设置的引用也会。

Because sometimes things should not be re-pointable.因为有时事情不应该重新指向。 (Eg, the reference to a Singleton.) (例如,对 Singleton 的引用。)

Because it's great in a function to know that your argument can't be null.因为在函数中知道你的参数不能为空是很好的。

But mostly, because it allows use to have something that really is a pointer, but which acts like a local value object.但主要是因为它允许使用一些真正是指针的东西,但它的作用就像一个本地值对象。 C++ tries hard, to quote Stroustrup, to make class instances "do as the ints d".引用 Stroustrup 的话,C++ 努力使类实例“像 ints d 那样做”。 Passing an int by vaue is cheap, because an int fitss into a machine register.通过 vaue 传递 int 很便宜,因为 int 适合机器寄存器。 Classes are often bigger than ints, and passing them by value has significant overhead.类通常比整数大,按值传递它们有很大的开销。

Being able to pass a pointer (which is often the size of an int, or maybe two ints) that "looks like" a value object allows us to write cleaner code, without the "implementation detail" of dereferences.能够传递一个“看起来像”值对象的指针(通常是一个 int 的大小,或者可能是两个 int 的大小)允许我们编写更清晰的代码,而无需取消引用的“实现细节”。 And, along with operator overloading, it allows us to write classes use syntax similar to the syntax used with ints.并且,与运算符重载一起,它允许我们使用类似于 int 使用的语法来编写类。 In particular, it allows us to write template classes with syntax that can be equally applied to primitive, like ints, and classes (like a Complex number class).特别是,它允许我们编写具有同样适用于原始类型(如整数)和类(如复数类)的语法的模板类。

And, with operator overloading especially, there are places were we should return an object, but again, it's much cheaper to return a pointer.而且,特别是对于运算符重载,有些地方我们应该返回一个对象,但同样,返回一个指针要便宜得多。 Oncve again, returning a reference is our "out. Oncve 再次,返回引用是我们的“出局”。

And pointers are hard.指针很难。 Not for you, maybe, and not to anyone that realizes a pointer is just the value of a memory address.也许不适合您,也不适合任何意识到指针只是内存地址值的人。 But recalling my CS 101 class, they tripped up a number of students.但是回想起我的 CS 101 课,他们绊倒了许多学生。

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

can be confusing.可能会令人困惑。

Heck, after 40 years of C, people still can't even agree if a pointer declaration should be:哎呀,经过 40 年的 C 语言,人们甚至不能同意指针声明是否应该是:

char* p;

or或者

char *p;

I always wondered why they didn't make a reference assignment operator (say :=) for this.我一直想知道为什么他们没有为此创建引用赋值运算符(例如:=)。

Just to get on someone's nerves I wrote some code to change the target of a reference in a structure.为了让某人紧张,我编写了一些代码来更改结构中引用的目标。

No, I do not recommend repeating my trick.不,我不建议重复我的伎俩。 It will break if ported to a sufficiently different architecture.如果移植到一个完全不同的架构,它就会崩溃。

Being half serious: IMHO to make them little more different from pointers ;) You know that you can write:半认真:恕我直言,让它们与指针有点不同;) 你知道你可以写:

MyClass & c = *new MyClass();

If you could also later write:如果你以后也可以这样写:

c = *new MyClass("other")

would it make sense to have any references alongside with pointers?在指针旁边有任何引用是否有意义?

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM