【问题标题】:Why are arrays of references illegal?为什么引用数组是非法的?
【发布时间】:2010-11-12 22:49:24
【问题描述】:

以下代码无法编译。

int a = 1, b = 2, c = 3;
int& arr[] = {a,b,c,8};

C++ 标准对此有何评论?

我知道我可以声明一个包含引用的类,然后创建该类的数组,如下所示。但是我很想知道为什么上面的代码不能编译。

struct cintref
{
    cintref(const int & ref) : ref(ref) {}
    operator const int &() { return ref; }
private:
    const int & ref;
    void operator=(const cintref &);
};

int main() 
{
  int a=1,b=2,c=3;
  //typedef const int &  cintref;
  cintref arr[] = {a,b,c,8};
}

可以使用struct cintref 代替const int & 来模拟引用数组。

【问题讨论】:

  • 即使数组有效,在其中存储原始 '8' 值也行不通。如果你做了“intlink value = 8;”,它会死得很惨,因为它几乎只是翻译成“const int & value = 8;”。引用必须引用变量。
  • intlink value = 8; 确实有效。看看你是否不相信。
  • 正如 Alexey 所指出的,将右值绑定到 const 引用是完全有效的。
  • 不起作用的是operator=。不能重新安装引用。如果你真的想要这样的语义——尽管我个人没有发现它们实际上有用的情况——那么std::reference_wrapper 将是这样做的方法,因为它实际上存储了一个指针但提供了类似引用的operator s 和 确实 允许重新安装。但是我只需要一个指针!
  • operator= 是私有的,未实现,也就是 C++11 中的 =delete。

标签: c++ arrays reference


【解决方案1】:

回答您关于标准的问题,我可以引用 C++ 标准 §8.3.2/4

不得有对引用的引用,不得有引用数组,也不得有指向引用的指针。

那是因为引用不是对象,不占用内存,所以没有地址。您可以将它们视为对象的别名。声明一个什么都没有的数组没有多大意义。

【讨论】:

  • 还有什么要说的?
  • 应该有引用数组,因为我们有指针数组,相同的信息但不同的处理,不是吗?
  • 如果编译器开发人员确实决定作为扩展允许引用数组,那么它会成功吗?或者是否有一些真正的问题——也许是模棱两可的代码——会让定义这个扩展变得过于混乱?
  • @AaronMcDaid 我认为真正的原因 - 在这个和类似的讨论中如此明显地缺席 - 是如果一个人有一系列参考资料,那么人们怎么可能消除两者之间的歧义?元素的地址及其所指对象的地址?对“您不能在数组/容器/任何东西中放置引用”的直接反对意见是,您可以放置一个 struct,其唯一成员是引用。但是,通过这样做,您现在已经获得了要命名的引用和父对象……这意味着您现在可以明确说明您想要哪个地址。似乎很明显……那为什么没有人说呢?
  • @polyglot What more is there to say? 为什么的基本原理?我完全是关于标准的,但只是引用它并假设这是讨论的结束似乎是摧毁用户批判性思维的必经之路 - 以及语言发展的任何潜力,例如超出遗漏或人为限制的限制。有太多'因为标准在这里说' - 而不够',由于以下实际原因,这样说是非常明智的'。我不知道为什么对只说前者甚至没有尝试探索后者的答案如此热切地投赞成票。
【解决方案2】:

引用不是对象。它们没有自己的存储空间,它们只是引用现有对象。因此,引用数组没有意义。

如果你想要一个引用另一个对象的轻量级对象,那么你可以使用指针。如果您为所有 struct 实例的所有引用成员提供显式初始化,您将只能使用带有引用成员的 struct 作为数组中的对象。引用不能默认初始化。

编辑:正如 jia3ep 所指出的,在声明的标准部分中,明确禁止引用数组。

【讨论】:

  • 引用在本质上与常量指针相同,因此它们会占用一些内存(存储)来指向某个东西。
  • 没有必要。例如,如果地址是对本地对象的引用,编译器可能能够避免存储地址。
  • 不一定。结构中的引用通常会占用一些存储空间。本地参考通常不会。无论哪种方式,在严格的标准意义上,引用都不是对象,并且(这对我来说是新的)命名引用实际上不是变量
  • 是的,但这是一个实现细节。 C++ 模型中的object 是一个类型化的存储区域。引用明确地不是对象,并且不能保证它在任何特定上下文中都会占用存储空间。
  • 这样说:你可以拥有一个只有 16 个 foo 引用的结构,并以与我想使用数组完全相同的方式使用它of refs - 除了你不能索引到 16 个 foo 引用。这是一个语言疣恕我直言。
【解决方案3】:

这是一个有趣的讨论。显然,引用数组是完全非法的,但恕我直言,原因并不是说“它们不是对象”或“它们没有大小”那么简单。我要指出,数组本身并不是 C/C++ 中的成熟对象——如果你反对,请尝试使用数组作为“类”模板参数来实例化一些 stl 模板类,看看会发生什么。你不能返回它们,分配它们,将它们作为参数传递。 (数组参数被视为指针)。但是制作数组的数组是合法的。 引用确实具有编译器可以并且必须计算的大小 - 您不能 sizeof() 引用,但您可以创建一个只包含引用的结构。它的大小足以包含所有实现引用的指针。如果不初始化所有成员,就无法实例化这样的结构:

struct mys {
 int & a;
 int & b;
 int & c;
};
...
int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs.a += 3  ;  // add 3 to ivar1

其实你可以在结构体定义中加入这一行

struct mys {
 ...
 int & operator[]( int i ) { return i==0?a : i==1? b : c; }
};

...现在我有一些看起来很像 refs 数组的东西:

int ivar1, ivar2, arr[200];
mys my_refs = { ivar1, ivar2, arr[12] };

my_refs[1] = my_refs[2]  ;  // copy arr[12] to ivar2
&my_refs[0];               // gives &my_refs.a == &ivar1

现在,这不是一个真正的数组,它是一个运算符重载;例如,它不会做数组通常会做的事情,例如 sizeof(arr)/sizeof(arr[0])。但它使用完全合法的 C++ 完成了我想要一组引用做的事情。 除了 (a) 设置超过 3 或 4 个元素很痛苦,并且 (b) 它正在使用一堆 ?: 进行计算,这可以使用索引来完成(不是使用普通的 C-pointer-calculation-semantics 索引,但仍然索引)。我希望看到一个非常有限的“引用数组”类型实际上可以做到这一点。 IE。引用数组不会被视为作为引用的事物的一般数组,而是它将是一个新的“引用数组”事物,它有效地映射到与上述类似的内部生成的类(但你不幸的是不能用模板制作)。

如果您不介意这种讨厌的事情,这可能会起作用:将 '*this' 重铸为 int * 的数组并返回一个引用:(不推荐,但它显示了正确的'array' 会起作用):

 int & operator[]( int i ) { return *(reinterpret_cast<int**>(this)[i]); }

【讨论】:

  • 可以在 C++11 std::tuple&lt;int&amp;,int&amp;,int&amp;&gt; abcref = std::tie( a,b,c) 中执行此操作 - 它创建了一个引用“数组”,只能使用 std:: 编译时常量进行索引得到。但是你不能做std::array&lt;int&amp;,3&gt; abcrefarr = std::tie( a,b,c)。也许有一种我不知道的方法。在我看来,应该有一种方法可以编写 std::array&lt;T&amp;,N&gt; 的特化,can 可以做到这一点 - 因此函数 std::tiearray(...) 返回一个这样的函数(或允许 std::array&lt;T&amp;,N&gt; 接受初始化程序包含地址 = {&v1, &v2 等})
  • 你的提议很愚蠢(IMO),你的最后一行假设一个 int 可以保存一个指针(通常是错误的,至少在我工作的地方)。但这是这里唯一的答案,有一个合理的理由说明为什么禁止引用数组,所以+1
  • @Nemo 这并不是真正的提议,只是为了说明这种东西的功能表面上并不荒谬。我在上一条评论中指出,c++ 有一些接近的东西。此外,您引用的最后一行假定实现对 int 的 reference 的内存可以有用地别名为指向 int 的指针——结构包含引用,而不是 int——这往往是真的。我并没有声称它是可移植的(甚至不受“未定义行为”规则的影响)。它旨在说明如何在后台使用索引来避免所有?:
  • 很公平。但我确实认为这个想法“表面上很荒谬”,这正是不允许引用数组的原因。数组首先是一个容器。所有容器都需要一个迭代器类型才能应用标准算法。 “T 数组”的迭代器类型通常是“T *”。引用数组的迭代器类型是什么?对于一个本质上无用的功能来说,处理所有这些问题所需的语言黑客数量是荒谬的。实际上,也许我会把这个作为我自己的答案:-)
  • @Nemo 它不需要是核心语言的一部分,所以迭代器类型为“自定义”没什么大不了的。所有各种容器类型都有其迭代器类型。在这种情况下,取消引用的迭代器将引用目标 obj,而不是数组中的 ref,这与数组的行为完全一致。它可能仍然需要一些新的 C++ 行为来支持作为模板非常简单的std::array&lt;T,N&gt;,很容易在应用程序代码中定义,直到 c++11 才被添加到 std::,大概是因为它有这个问题没办法= {1,2,3};那是“语言黑客”吗?
【解决方案4】:

评论您的编辑:

更好的解决方案是std::reference_wrapper

详情: http://www.cplusplus.com/reference/functional/reference_wrapper/

例子:

#include <iostream>
#include <functional>
using namespace std;

int main() {
    int a=1,b=2,c=3,d=4;
    using intlink = std::reference_wrapper<int>;
    intlink arr[] = {a,b,c,d};
    return 0;
}

【讨论】:

  • 嗨,这在 09 年就已经说过了。提示寻找更新的帖子 :)
  • 这篇文章很旧,但并不是每个人都知道使用 reference_wrapper 的解决方案。人们需要阅读有关 C++11 的 STL 和 STDlib 的内容。
  • 这给出了一个链接和示例代码,而不是仅仅提到类的名称reference_wrapper
【解决方案5】:

数组可以隐式转换为指针,pointer-to-reference is illegal in C++

【讨论】:

  • 确实不能拥有指向引用的指针,但这并不是不能拥有引用数组的原因。相反,它们都是引用不是对象这一事实的征兆。
  • 一个结构只能包含引用,它的大小与引用的数量成正比。如果您确实有一个 refs 'arr' 数组,则正常的数组到指针的转换将没有意义,因为 'pointer-to-reference' 没有意义。 但是 只使用 arr[i] 是有意义的,就像 st.ref 一样,当 'st' 是一个包含 ref 的结构时。唔。但是 &arr[0] 会给出第一个引用对象的地址,而 &arr[1]- &arr[0] 不会与 &arr[2]-&arr[1] 相同 - 它会产生很多奇怪。
【解决方案6】:

给定int&amp; arr[] = {a,b,c,8};sizeof(*arr) 是什么?

在其他任何地方,引用都被简单地视为事物本身,因此sizeof(*arr) 应该只是sizeof(int)。但这会使该数组上的数组指针算术错误(假设引用的宽度不同是整数)。为了消除歧义,这是禁止的。

【讨论】:

  • 是的。除非它有大小,否则你不能拥有一个数组。参考的大小是多少?
  • @DavidSchwartz 创建一个struct,其唯一成员是该引用并找出...?
  • 这应该是公认的答案,而不是简单地把标准扔给 OP 的愚蠢迂腐的答案。
  • 可能有错别字。 “假设引用的宽度不同是整数”应该是“假设引用的宽度与整数不同”。将 is 更改为 as
  • 但是等等,按照这个逻辑你不能有引用成员变量。
【解决方案7】:

因为就像许多人在这里所说的那样,引用不是对象。它们只是别名。确实,某些编译器可能将它们实现为指针,但标准并未强制/指定这一点。因为引用不是对象,所以你不能指向它们。将元素存储在数组中意味着存在某种索引地址(即指向某个索引处的元素);这就是为什么你不能有引用数组的原因,因为你不能指向它们。

使用 boost::reference_wrapper 或 boost::tuple 代替;或者只是指针。

【讨论】:

    【解决方案8】:

    我相信答案很简单,它与引用的语义规则以及数组在 C++ 中的处理方式有关。

    简而言之: 引用可以被认为是没有默认构造函数的结构,所以所有相同的规则都适用。

    1) 从语义上讲,引用没有默认值。只能通过引用某些东西来创建引用。引用没有表示没有引用的值。

    2) 当分配一个大小为 X 的数组时,程序会创建一个默认初始化对象的集合。由于引用没有默认值,因此创建这样的数组在语义上是非法的。

    这条规则也适用于没有默认构造函数的结构/类。以下代码示例无法编译:

    struct Object
    {
        Object(int value) { }
    };
    
    Object objects[1]; // Error: no appropriate default constructor available
    

    【讨论】:

    • 您可以创建一个Object 的数组,您只需确保将它们全部初始化:Object objects[1] = {Object(42)};。同时,即使初始化所有引用,也无法创建引用数组。
    【解决方案9】:

    您可以非常接近这个模板结构。 但是,您需要使用指向 T 而不是 T 的指针的表达式进行初始化;因此,尽管您可以类似地轻松制作“fake_constref_array”,但您将无法像 OP 的示例(“8”)中那样将其绑定到右值;

    #include <stdio.h>
    
    template<class T, int N> 
    struct fake_ref_array {
       T * ptrs[N];
      T & operator [] ( int i ){ return *ptrs[i]; }
    };
    
    int A,B,X[3];
    
    void func( int j, int k)
    {
      fake_ref_array<int,3> refarr = { &A, &B, &X[1] };
      refarr[j] = k;  // :-) 
       // You could probably make the following work using an overload of + that returns
       // a proxy that overloads *. Still not a real array though, so it would just be
       // stunt programming at that point.
       // *(refarr + j) = k  
    }
    
    int
    main()
    {
        func(1,7);  //B = 7
        func(2,8);     // X[1] = 8
        printf("A=%d B=%d X = {%d,%d,%d}\n", A,B,X[0],X[1],X[2]);
            return 0;
    }
    

    --> A=0 B=7 X = {0,8,0}

    【讨论】:

      【解决方案10】:

      只是为了添加到所有对话中。 由于数组需要连续的内存位置来存储项目,所以如果我们创建一个引用数组,那么不能保证它们会在连续的内存位置,所以访问将是一个问题,因此我们甚至不能应用所有的数学运算数组。

      【讨论】:

      • 考虑原始问题中的以下代码:int a = 1, b = 2, c = 3; int& arr[] = {a,b,c,8}; a、b、c 驻留在连续内存位置的可能性非常小。
      • 这远不是在容器中保存引用概念的主要问题。
      【解决方案11】:

      参考对象没有大小。如果你写sizeof(referenceVariable),它会给你referenceVariable引用的对象的大小,而不是引用本身的大小。它没有自己的大小,这就是编译器无法计算数组需要多少大小的原因。

      【讨论】:

      • 如果是这样,那么编译器如何计算仅包含 ref 的结构的大小?
      • 编译器确实知道引用的物理大小 - 它与指针相同。事实上,引用是语义上美化的指针。指针和引用之间的区别在于,引用有相当多的语义规则,以减少与指针相比可能创建的错误数量。
      【解决方案12】:

      当您在数组中存储某些内容时,需要知道其大小(因为数组索引依赖于大小)。根据 C++ 标准,未指定引用是否需要存储,因此无法对引用数组进行索引。

      【讨论】:

      • 但是通过将上述引用放在struct 中,神奇地一切都变得指定、明确、有保证并且具有确定的大小。那么,为什么会存在这种看似完全人为的差异呢?
      • ...当然,如果有人真正开始思考包装 struct 所赋予的意义,那么一些可能的好答案就会开始自我暗示——但对我来说,没有人这样做,尽管这是对为什么你不能使用未包装的引用的所有常见理由的直接挑战之一。
      【解决方案13】:

      考虑一个指针数组。指针实际上是一个地址;因此,当您初始化数组时,您类似地告诉计算机,“分配这块内存来保存这些 X 数字(它们是其他项目的地址)。”然后,如果您更改其中一个指针,您只是在更改它所指向的内容;它仍然是一个数字地址,它本身就在同一个位置。

      引用类似于别名。如果你要声明一个引用数组,你基本上会告诉计算机,“分配这个由分散在各处的所有这些不同项目组成的无定形内存块。”

      【讨论】:

      • 那么,为什么创建一个唯一成员是引用的struct 会产生完全合法、可预测和非无定形的东西?为什么用户必须为它包装一个引用才能神奇地获得数组能力,或者它只是一个人为的限制? IMO 没有直接解决这个问题的答案。我假设反驳是'包装对象确保您可以以包装对象的形式使用值语义,因此它确保您可以获得值的保证,这是因为例如如何消除元素和裁判地址之间的歧义'......你是这个意思吗?
      【解决方案14】:

      实际上,这是 C 和 C++ 语法的混合体。

      您应该或者使用纯 C 数组,它不能是引用,因为引用只是 C++ 的一部分。 或者您采用 C++ 方式并使用 std::vectorstd::array 类来实现您的目的。

      至于编辑部分:尽管struct 是来自C 的元素,但您定义了构造函数和运算符函数,使其成为C++ class。因此,您的 struct 不会在纯 C 中编译!

      【讨论】:

      • 你的意思是什么? std::vectorstd::array 也不能包含引用。 C++ 标准非常明确,任何容器都不能。
      猜你喜欢
      • 2012-08-08
      • 1970-01-01
      • 1970-01-01
      • 2011-07-15
      • 1970-01-01
      • 2012-01-17
      • 2019-04-04
      • 1970-01-01
      • 2017-12-06
      相关资源
      最近更新 更多