为什么引用数组不合法?


149

以下代码无法编译。

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 &模拟引用数组。


1
即使该数组有效,在其中存储原始的“ 8”值也不起作用。如果您执行了“ intlink value = 8;”,那将可怕地死掉,因为它几乎被转换为“ const int&value = 8;”。引用必须引用变量。
格兰特·彼得斯

3
intlink value = 8;确实有效。检查您是否不相信。
阿列克谢·马里斯托夫

7
正如Alexey指出的,将右值绑定到const引用是完全有效的。
avakar,2009年

1
什么工作是operator=。引用不能重新设置。如果您确实想要这样的语义-尽管我个人还没有发现它们实际上有用的情况-那么std::reference_wrapper将是这样做的方法,因为它实际上存储了一个指针但提供了类似引用operator的,并且确实允许重新定位。但是然后我只用一个指针!
underscore_d

1
operator =是私有的,未实现,也就是C ++ 11中的= delete。
Jimmy Hartzell

Answers:


148

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

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


31
还有什么要说的?
polyglot

9
出于相同的原因,应该有一个引用数组,因为我们有多个指针数组,相同的信息但处理方式不同,不是吗?
neu-rah 2014年

6
如果编译器开发人员确实决定允许引用数组作为扩展,那会成功吗?还是有一些真正的问题-也许是模棱两可的代码-会使定义此扩展太令人困惑了?
亚伦·麦克戴德

23
@AaronMcDaid我认为,真正的原因-在本次和类似的讨论中如此明显地缺失了-如果一个人拥有一系列引用,那么如何在一个元素的地址及其引用对象的地址之间消除歧义?对“您不能将引用放入数组/容器/任何对象”的直接反对是,您可以放置struct其唯一成员是引用的。但是,这样做之后,您现在已经有了一个引用和一个父对象来命名……这意味着您可以明确地声明所需的地址。似乎很明显……那为什么没人说呢?
underscore_d

95
@polyglot What more is there to say?一种理由为何?我是关于标准的,但只是引用它并假设讨论的结束似乎是销毁用户批判性思维的必由之路-以及语言发展的任何潜力,例如,超出了遗漏或限制的范围。人为限制。这里有太多的“因为标准说了”,而没有足够的“并且出于以下实际原因,这样说是非常明智的”。我不知道为什么投票如此激烈地回答只说前者而没有尝试探索后者的答案。
underscore_d

64

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

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

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


6
引用在本质上与常量指针相同,因此它们占用一些内存(存储空间)以指向某个对象。
inazaruk

7
不一定。例如,如果它是对本地对象的引用,则编译器可能能够避免存储地址。
EFraim

4
不必要。结构中的引用通常会占用一些存储空间。本地参考通常不这样做。无论哪种方式,从严格的标准意义上讲,引用不是对象,并且(对我而言这是新的)命名引用实际上不是变量
CB Bailey 2009年

26
是的,但这是一个实现细节。C ++模型中的对象是存储的类型化区域。引用显然不是对象,并且不能保证它会在任何特定上下文中占用存储空间。
CB Bailey 2009年

9
这样说:您可以拥有一个仅16个foo引用的结构,并以与我要使用我的refs数组完全相同的方式使用它-除了无法索引16个foo引用之外。这是语言疣恕我直言。
greggo 2011年

28

这是一个有趣的讨论。显然,裁判的排列完全是非法的,但是恕我直言,其原因并不像说“它们不是对象”或“它们没有大小”那样简单。我要指出的是,数组本身并不是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; }
};

...现在我有一些东西看起来像很多参考:

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指针计算语义索引) ,但仍会建立索引)。我想看到一个非常有限的“引用数组”类型,它实际上可以做到这一点。也就是说,引用数组不会被视为是引用的事物的通用数组,而是一个新的“引用数组” 用模板制作)。

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

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

1
可以在C ++ 11中执行此操作-C ++ 11 std::tuple<int&,int&,int&> abcref = std::tie( a,b,c)创建引用的“数组”,这些引用只能由使用std :: get的编译时常量进行索引。但是你做不到std::array<int&,3> abcrefarr = std::tie( a,b,c)。也许有一种我不知道的方法。在我看来,应该有一种方法可以写出一种专门的std::array<T&,N>方法做到这一点-因此一个函数std::tiearray(...)可以返回一个这样的函数(或允许std::array<T&,N>接受包含地址= {&v1,&v2等}的初始化程序)
greggo

您的建议很愚蠢(IMO),最后一行假设一个int可以容纳一个指针(通常为false,至少在我工作的地方)。但这是这里唯一有合理理由的答案,这是为什么要禁止引用数组的原因,所以+1
Nemo

@Nemo并不是真正的提议,只是为了说明这样的功能在表面上并不荒谬。我在最后一条评论中指出,c ++的功能非常接近。同样,您所指的最后一行假设实现对int 的引用的内存可以有用地用作指向int的指针的别名(该结构包含引用,而不是int),并且这往往是正确的。我并不是说它是便携式的(甚至不受“未定义行为”规则的约束)。它旨在说明如何在引擎盖下使用索引来避免所有?:
greggo

很公平。但是我确实认为这个想法是“荒谬的”,这就是为什么不允许引用数组的原因。数组首先是一个容器。所有容器都需要迭代器类型,才能应用标准算法。“ T的数组”的迭代器类型通常为“ T *”。引用数组的迭代器类型是什么?对于本质上无用的功能,处理所有这些问题所需的大量语言骇客行为是荒谬的。实际上,也许我会自己回答这个问题:-)
Nemo

@Nemo不需要成为核心语言的一部分,因此迭代器类型为“ custom”没什么大不了的。所有各种容器类型都有其迭代器类型。在这种情况下,取消引用的迭代器将引用目标obj,而不是数组中的ref,这与数组的行为完全一致。可能还需要一些新的C ++行为才能作为模板支持。std::array<T,N>在应用程序代码中非常简单,容易定义的方法并未添加到std ::中,直到c ++ 11为止,大概是因为在没有这种方法的情况下很谨慎做= {1,2,3};的是,“语言入侵”?
greggo

28

评论您的编辑:

更好的解决方案是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提示找新的职位:)
Stígandr

9
该帖子很旧,但并不是每个人都知道使用reference_wrapper解决方案。人们需要阅读有关C ++ 11的STL和STDlib的信息。
Youw

这提供了链接和示例代码,而不仅仅是提及类的名称reference_wrapper
David Stone

12

数组可以隐式转换为指针,并且在C ++中指向引用的指针是非法的


11
的确,您不能具有指向引用的指针,但这不是不能包含引用数组的原因。而是它们都是引用不是对象这一事实的症状。
理查德·科登,2009年

1
一个结构只能包含引用,并且其大小与引用的数量成正比。如果确实有一个refs'arr'数组,则正常的数组到指针的转换将没有意义,因为'pointer-to-reference'没有意义。但是当'st'是包含ref的结构时,以与st.ref相同的方式仅使用arr [i]是有意义的。嗯 但是&arr [0]将给出第一个引用对象的地址,而&arr [1]-&arr [0]与&arr [2]-&arr [1]不同-这会产生很多奇怪的地方。
greggo 2011年

11

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

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


是的 除非具有大小,否则您无法拥有某种东西的数组。参考的大小是多少?
David Schwartz

4
@DavidSchwartz使struct该参考的唯一成员成为会员并找到..?
underscore_d

3
这应该是公认的答案,而不是愚蠢的学徒式的答案,后者只是在OP上提出了标准。
泰森·雅各布斯

也许有错字。“假定引用的宽度不相同是整数”应为“假定引用的宽度不相同是整数”。改变
zhenguoli

但是,等等,通过这种逻辑,您将没有引用成员变量。
泰森·雅各布斯

10

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

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


5

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

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

1)在语义上,引用没有默认值。引用只能通过引用某些内容来创建。引用没有值来表示没有引用。

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

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

struct Object
{
    Object(int value) { }
};

Object objects[1]; // Error: no appropriate default constructor available

1
您可以创建的数组Object,只需确保将它们全部初始化即可Object objects[1] = {Object(42)};。同时,即使初始化所有引用,也无法创建引用数组。
Anton3

4

您可以使用此模板结构相当接近。但是,您需要使用指向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}


2

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


3
如果是这样,那么编译器如何计算仅包含引用的结构的大小?
贾斯特

编译器确实知道引用的物理大小-它与指针相同。引用实际上是在语义上夸耀的指针。指针和引用之间的区别在于,引用上有很多语义规则,以减少与指针相比可能创建的错误的数量。
Kristupas A.

2

当您将某物存储在数组中时,需要知道其大小(因为数组索引依赖于大小)。根据C ++标准,尚不确定引用是否需要存储,因为无法对引用数组进行索引。


1
但是,通过将所述引用放入struct,可以神奇地使所有内容都变得明确,明确,有保证且具有确定性。因此,为什么存在这种看似完全人为的差异?
underscore_d

...当然,如果人们真的开始考虑包装 struct,那么就会出现一些好的可能的答案,但对我来说,还没有人这样做,尽管这是所有普通大众面临的直接挑战之一为什么不能使用未包装引用的理由。
underscore_d

2

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


考虑以下来自原始问题的代码:int a = 1,b = 2,c = 3;int&arr [] = {a,b,c,8}; a,b,c驻留在连续内存位置的可能性很小。
阿米特·库玛

这与将引用保存在容器中的概念相去甚远。
underscore_d

0

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

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


1
那么,为什么struct只让其唯一成员作为参考对象却会导致某些事情完全合法,可预测且非变性?用户为什么必须为其包装一个引用以神奇地获得具有阵列功能的功能,还是仅仅是人为的限制?海事组织没有答案直接解决了这个问题。我认为反驳是“包装对象确保您可以使用包装对象形式的值语义,因此它确保您可以得到值的保证,这是必需的,因为例如,如何在元素和裁判地址之间消除歧义” ... 这是你的意思吗?
underscore_d

-2

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

应该要么使用纯C数组,不能引用的,因为参考是唯一的C ++一部分。或者,您采用C ++的方式并根据需要使用std::vectoror std::array类。

至于被编辑的部分:即使structC是来自C的元素,您也可以定义一个构造函数和运算符函数,使其成为C ++ class。因此,您struct将无法在纯C中编译!


你想说什么?std::vectorstd::array也不能包含引用。C ++标准非常明确,没有容器可以做到。
underscore_d
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.