Por que matrizes de referências são ilegais?

149

O código a seguir não compila.

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

O que o padrão C ++ diz sobre isso?

Eu sei que posso declarar uma classe que contém uma referência e criar uma matriz dessa classe, como mostrado abaixo. Mas eu realmente quero saber por que o código acima não é compilado.

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

É possível usar em struct cintrefvez de const int &simular uma matriz de referências.

Alexey Malistov
fonte
1
Mesmo que a matriz fosse válida, armazenar um valor '8' bruto não funcionaria. Se você fizesse "intlink value = 8;", ele morreria horrivelmente, porque é praticamente traduzido para "const int & value = 8;". Uma referência deve fazer referência a uma variável.
Grant Peters
3
intlink value = 8;funciona. verifique se você não acredita.
Alexey Malistov 22/07/2009
7
Como Alexey aponta, é perfeitamente válido vincular um rvalue a uma referência const.
avakar 22/07/2009
1
O que não funciona é isso operator=. As referências não podem ser recolocadas. Se você realmente quer tal semântica - embora eu não tenho, pessoalmente, encontrou uma situação em que eles são praticamente útil - então std::reference_wrapperseria a maneira de fazê-lo, uma vez que, na verdade, armazena um ponteiro, mas fornece a referência semelhantes operators e não permitir recolocar. Mas então eu usaria um ponteiro!
Underscore_d
1
O operador = é privado e não implementado, também conhecido como no C ++ 11 é = delete.
precisa

Respostas:

148

Respondendo à sua pergunta sobre o padrão, posso citar o Padrão C ++ §8.3.2 / 4 :

Não deve haver referências a referências, nem matrizes de referências nem ponteiros para referências.

Kirill V. Lyadvinsky
fonte
32
O que mais há pra dizer?
poliglota
9
deve haver matrizes de referências pela mesma razão que temos matrizes de ponteiros, mesma informação, mas manipulação diferente, não?
Neu-rah
6
Se os desenvolvedores do compilador decidissem, como uma extensão, permitir matrizes de referências, seria um sucesso? Ou existem alguns problemas reais - talvez código ambíguo - que apenas tornariam muito confuso definir essa extensão?
Aaron McDaid
23
@AaronMcDaid Eu acho que a verdadeira razão - que está tão ausente dessa discussão e de discussões semelhantes - é que se alguém tivesse uma série de referências, como seria possível desambiguar o endereço de um elemento e o endereço de seu referente? A objeção imediata a 'Você não pode colocar uma referência em uma matriz / contêiner / o que quer que seja' é que você pode colocar um structcujo único membro seja uma referência. Mas então, ao fazer isso, você agora tem uma referência e um objeto pai para nomear ... o que agora significa que você pode declarar sem ambiguidade qual endereço deseja. Parece óbvio ... então por que ninguém disse isso?
Underscore_d
95
@polyglot What more is there to say?Uma justificativa para o porquê ? Sou a favor do Padrão, mas apenas citá-lo e supor que esse é o fim da discussão parece uma maneira infalível de destruir o pensamento crítico dos usuários - junto com qualquer potencial para a linguagem evoluir, por exemplo, além das limitações de omissão ou restrição artificial. Há muito 'porque o Padrão diz' aqui - e não o suficiente 'e é muito sensato dizer isso, devido às seguintes razões práticas'. Não sei por que os votos positivos fluem tão ansiosamente para respostas que dizem apenas o primeiro sem sequer tentar explorá-lo.
Underscore_d
64

Referências não são objetos. Eles não têm armazenamento próprio, apenas referenciam objetos existentes. Por esse motivo, não faz sentido ter matrizes de referências.

Se você deseja um objeto leve que faça referência a outro objeto, use um ponteiro. Você só poderá usar a structcom um membro de referência como objetos em matrizes se fornecer inicialização explícita para todos os membros de referência para todas as structinstâncias. As referências não podem ser inicializadas por padrão.

Edit: Como jia3ep observa, na seção padrão sobre declarações, há uma proibição explícita de matrizes de referências.

CB Bailey
fonte
6
As referências são iguais em sua natureza como ponteiros constantes e, portanto, elas ocupam alguma memória (armazenamento) para apontar para alguma coisa.
inazaruk 22/07/2009
7
Não necessariamente. O compilador pode evitar o armazenamento do endereço, se for uma referência ao objeto local, por exemplo.
EFraim 22/07/2009
4
Não necessariamente. Referências em estruturas geralmente ocupam algum armazenamento. Referências locais geralmente não. De qualquer maneira, no sentido estrito do padrão, as referências não são objetos e (isso era novo para mim) as referências nomeadas não são realmente variáveis .
CB Bailey
26
Sim, mas este é um detalhe de implementação. Um objeto no modelo C ++ é uma região de armazenamento digitada. Uma referência não é explicitamente um objeto e não há garantia de que consuma armazenamento em qualquer contexto específico.
CB Bailey
9
Coloque desta maneira: você pode ter uma estrutura de apenas 16 refs para foo e usá-lo exatamente da mesma maneira que eu gostaria de usar minha matriz de refs - exceto que você não pode indexar nas 16 referências foo . Esta é uma verruga de linguagem IMHO.
Greggo
28

Esta é uma discussão interessante. Claramente, matrizes de árbitros são totalmente ilegais, mas IMHO a razão pela qual não é tão simples como dizer 'eles não são objetos' ou 'eles não têm tamanho'. Eu apontaria que as próprias matrizes não são objetos completos no C / C ++ - se você se opuser a isso, tente instanciar algumas classes de modelo stl usando uma matriz como parâmetro de modelo de 'classe' e veja o que acontece. Você não pode devolvê-los, atribuí-los, passá-los como parâmetros. (um parâmetro de matriz é tratado como um ponteiro). Mas é legal fazer matrizes de matrizes. As referências têm um tamanho que o compilador pode e deve calcular - você não pode sizeof () uma referência, mas pode criar uma estrutura contendo nada além de referências. Ele terá um tamanho suficiente para conter todos os ponteiros que implementam as referências. Você pode'

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

Na verdade, você pode adicionar esta linha à definição de estrutura

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

... e agora eu tenho algo que se parece muito com uma variedade de referências:

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

Agora, isso não é uma matriz real, é uma sobrecarga de operador; ele não fará coisas que as matrizes normalmente fazem como sizeof (arr) / sizeof (arr [0]), por exemplo. Mas faz exatamente o que eu quero que uma matriz de referências faça, com C ++ perfeitamente legal. Exceto (a) é difícil configurar mais de 3 ou 4 elementos e (b) está fazendo um cálculo usando um monte de?: O que pode ser feito usando a indexação (não com a indexação normal de C-ponteiro-cálculo-semântica) , mas indexando). Eu gostaria de ver um tipo muito limitado de 'matriz de referência' que realmente pode fazer isso. Ou seja, uma matriz de referências não seria tratada como uma matriz geral de coisas que são referências, mas seria uma nova 'matriz de referência' fazer com modelos).

isso provavelmente funcionaria, se você não se importar com esse tipo de desagradável: reformule '* this' como uma matriz de int * 's e retorne uma referência feita a partir de uma: (não recomendado, mas mostra como a' matriz 'adequada trabalharia):

 int & operator[]( int i ) { return *(reinterpret_cast<int**>(this)[i]); }
Greggo
fonte
1
Você pode fazer isso no C ++ 11 std::tuple<int&,int&,int&> abcref = std::tie( a,b,c)- que cria uma "matriz" de referências que só podem ser indexadas por constantes em tempo de compilação usando std :: get. Mas você não pode fazer std::array<int&,3> abcrefarr = std::tie( a,b,c). Talvez haja uma maneira de fazer isso que eu não conheça. Parece-me que deve haver uma maneira de escrever uma especialização std::array<T&,N>que possa fazer isso - e, portanto, uma funçãostd::tiearray(...) que retorne uma dessas (ou permita std::array<T&,N>aceitar inicializador contendo endereços = {& v1, & v2 etc})
greggo
Sua proposta é boba (IMO) e sua última linha assume que um int pode conter um ponteiro (geralmente falso, pelo menos onde eu trabalho). Mas esta é a única resposta aqui com uma lógica legítima para porque são proibidos matrizes de referências, de modo +1
Nemo
@ Nemo não era realmente uma proposta, mas apenas para ilustrar que a funcionalidade de uma coisa dessas não é absurda. Observo no meu último comentário que c ++ tem coisas que se aproximam. Além disso, a última linha a que você se refere pressupõe que a memória implementando um referência a um int pode ser útil como um ponteiro para int - a estrutura contém referências, não ints - e isso tende a ser verdadeiro. Não estou afirmando que seja portátil (ou mesmo a salvo de regras de 'comportamento indefinido'). ele foi concebido para ilustrar como indexação pode ser usado sob o capô, para evitar todo o?:
Greggo
Justo. Mas eu acho que essa idéia é "absurda", e é exatamente por isso que matrizes de referências são proibidas. Uma matriz é, acima de tudo, um contêiner. Todos os contêineres precisam de um tipo de iterador para a aplicação de algoritmos padrão. O tipo de iterador para "matriz de T" é normalmente "T *". Qual seria o tipo de iterador para uma matriz de referências? A quantidade de hackers de linguagem necessários para lidar com todos esses problemas é ridícula para um recurso essencialmente inútil. Na verdade, talvez eu faça disso uma resposta minha :-)
Nemo
@ Nemo Não precisa fazer parte da linguagem principal, portanto o tipo de iterador sendo 'personalizado' não é grande coisa. Todos os vários tipos de contêineres têm seus tipos de iterador. Nesse caso, o iterador, sem referência, faria referência aos objetos de destino, não aos refs da matriz, o que é totalmente consistente com o comportamento da matriz. Pode ainda exigir um pouco do novo comportamento do C ++ para oferecer suporte como modelo. O muito simples std::array<T,N>, facilmente definido no código do aplicativo, não foi adicionado ao std :: até o c ++ 11, provavelmente porque é difícil ter isso sem nenhuma maneira de do = {1,2,3};Isso foi 'hacking de linguagem'?
Greggo
28

Comentário à sua edição:

Melhor solução é std::reference_wrapper .

Detalhes: http://www.cplusplus.com/reference/functional/reference_wrapper/

Exemplo:

#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;
}
Você W
fonte
Volta oi este foi indicado em 09. olhar Dica para as mensagens mais recentes :)
Stigandr
9
A postagem é antiga, mas nem todo mundo sabe sobre a solução com reference_wrapper. As pessoas precisam ler sobre STL e STDlib do C ++ 11.
youw
Isso fornece um link e um código de amostra, em vez de apenas mencionar o nome da classe reference_wrapper.
David Stone
12

Uma matriz é implicitamente convertível em um ponteiro, e o ponteiro para referência é ilegal em C ++

EFraim
fonte
11
É verdade que você não pode ter um ponteiro para uma referência, mas esse não é o motivo pelo qual você não pode ter uma matriz de referências. Em vez disso, ambos são sintomas do fato de que referências não são objetos.
Richard Corden
1
Uma estrutura pode conter apenas referências, e terá um tamanho proporcional ao número delas. Se você tivesse uma matriz de referências 'arr', a conversão normal de matriz para ponteiro não faria sentido, pois 'ponteiro para referência' não faria sentido. mas faria sentido usar arr [i], da mesma maneira que st.ref quando 'st' for uma estrutura contendo uma ref. Hmm. Mas & arr [0] forneceria o endereço do primeiro objeto referenciado e & arr [1] - & arr [0] não seria o mesmo que & arr [2] - & arr [1] - isso criaria muita estranheza.
Greggo
11

Dado int& arr[] = {a,b,c,8};, o que ésizeof(*arr) ?

Em qualquer outro lugar, uma referência é tratada como sendo simplesmente a coisa em si, sizeof(*arr)deveria simplesmente ser sizeof(int). Mas isso tornaria o ponteiro da matriz aritmético nessa matriz errado (supondo que as referências não tenham as mesmas larguras são ints). Para eliminar a ambiguidade, é proibido.

PaulMurrayCbr
fonte
Sim. Você não pode ter uma matriz de algo a menos que tenha um tamanho. Qual é o tamanho de uma referência?
David Schwartz
4
@DavidSchwartz Faça um structcujo único membro é essa referência e descubra ..?
underscore_d
3
Essa deve ser a resposta aceita, não a pedante estúpida que simplesmente lança o padrão no OP.
Tyson Jacobs
Talvez haja um erro de digitação. "assumindo que as referências não tenham as mesmas larguras são ints" deve ser "assumindo que as referências não tenham as mesmas larguras que as ints". Mudança é a como .
Zhenguoli
Mas espere, por essa lógica, você não poderia ter variáveis ​​de membro de referência.
Tyson Jacobs
10

Porque, como muitos disseram aqui, referências não são objetos. eles são simplesmente aliases. É verdade que alguns compiladores podem implementá-los como ponteiros, mas o padrão não força / especifica isso. E como as referências não são objetos, você não pode apontar para eles. Armazenar elementos em uma matriz significa que existe algum tipo de endereço de índice (ou seja, apontar para elementos em um determinado índice); e é por isso que você não pode ter matrizes de referências, porque não pode apontar para elas.

Use boost :: reference_wrapper ou boost :: tuple; ou apenas ponteiros.

navegador
fonte
5

Eu acredito que a resposta é muito simples e tem a ver com regras semânticas de referências e como as matrizes são tratadas em C ++.

Resumindo: as referências podem ser consideradas estruturas que não possuem um construtor padrão; portanto, todas as mesmas regras se aplicam.

1) Semântica, as referências não têm um valor padrão. As referências só podem ser criadas fazendo referência a algo. As referências não têm um valor para representar a ausência de uma referência.

2) Ao alocar uma matriz de tamanho X, o programa cria uma coleção de objetos inicializados por padrão. Como a referência não tem um valor padrão, a criação de uma matriz é semanticamente ilegal.

Esta regra também se aplica a estruturas / classes que não possuem um construtor padrão. O seguinte exemplo de código não compila:

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

Object objects[1]; // Error: no appropriate default constructor available
Kristupas A.
fonte
1
Você pode criar uma matriz de Object, você apenas tem que ter certeza de inicializar todos eles: Object objects[1] = {Object(42)};. Enquanto isso, você não pode criar uma matriz de referências, mesmo se inicializar todas elas.
Anton3 30/03
4

Você pode se aproximar bastante dessa estrutura do modelo. No entanto, você precisa inicializar com expressões que apontam para T, em vez de T; e assim, embora você possa criar facilmente um 'fake_constref_array' da mesma forma, não poderá vincular isso a rvalues, como foi feito no exemplo do 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}

Greggo
fonte
2

Um objeto de referência não tem tamanho. Se você escrever sizeof(referenceVariable), ele fornecerá o tamanho do objeto referenciado por referenceVariable, não o da própria referência. Ele não tem tamanho próprio, e é por isso que o compilador não pode calcular quanto tamanho a matriz exigiria.

Carl Seleborg
fonte
3
Se sim, como um compilador pode calcular o tamanho de uma estrutura contendo apenas uma referência?
Juster
O compilador conhece o tamanho físico de uma referência - é o mesmo que um ponteiro. As referências são, de fato, ponteiros semanticamente glorificados. A diferença entre ponteiros e referências é que as referências possuem algumas regras semânticas, a fim de reduzir o número de bugs que você pode criar em comparação com os ponteiros.
21418 Kristupas A.
2

Quando você armazena algo em uma matriz, seu tamanho precisa ser conhecido (uma vez que a indexação da matriz depende do tamanho). De acordo com o padrão C ++, não é especificado se uma referência requer ou não armazenamento; como resultado, a indexação de uma matriz de referências não seria possível.

Pradyot
fonte
1
Mas, colocando a referida referência em a struct, magicamente tudo se torna especificado, bem definido, garantido e de tamanho determinístico. Por que, portanto, existe essa diferença aparentemente totalmente artificial?
Underscore_d
... é claro, se alguém realmente começa a pensar sobre o que a embalagem structconfere, algumas boas respostas possíveis começam a se sugerir - mas para mim, ninguém o fez ainda, apesar de esse ser um dos desafios imediatos para todos os aspectos comuns. razões pelas quais você não pode usar referências desembrulhadas.
Underscore_d
2

Apenas para adicionar a toda a conversa. Como as matrizes requerem locais de memória consecutivos para armazenar o item, por isso, se criarmos uma matriz de referências, não é garantido que elas estejam em local de memória consecutiva, portanto, o acesso será um problema e, portanto, não podemos aplicar todas as operações matemáticas em array.

Amit Kumar
fonte
Considere o código abaixo da pergunta original: int a = 1, b = 2, c = 3; int & arr [] = {a, b, c, 8}; Há muito menos possibilidade de que a, b, c residam em um local de memória consecutivo.
Amit Kumar #
Isso está longe de ser o principal problema com o conceito de manter referências em um contêiner.
underscore_d
0

Considere uma variedade de ponteiros. Um ponteiro é realmente um endereço; portanto, quando você inicializa a matriz, informa analogicamente ao computador "aloque esse bloco de memória para armazenar esses números X (que são endereços de outros itens)". Então, se você alterar um dos ponteiros, estará apenas mudando para o que ele aponta; ainda é um endereço numérico que está no mesmo local.

Uma referência é análoga a um alias. Se você declarasse uma série de referências, basicamente diria ao computador: "aloque essa bolha amorfa de memória que consiste em todos esses itens diferentes espalhados".

Dan Tao
fonte
1
Por que, então, formar um structmembro cujo único é uma referência resulta em algo completamente legal, previsível e não amorfo? Por que um usuário deve encapsular uma referência para obter magicamente poderes capazes de array, ou é apenas uma restrição artificial? OMI nenhuma resposta abordou diretamente isso. Suponho que a refutação seja 'O objeto de empacotamento garante que você possa usar a semântica de valor, na forma do objeto de empacotamento, para garantir que você tenha as garantias de valores necessárias, porque, por exemplo, como desambiguar o elemento e o endereço do árbitro' ... É isso que você quis dizer?
Underscore_d
-2

Na verdade, essa é uma mistura da sintaxe C e C ++.

Deverá quer usar matrizes C puras, que não pode ser de referências, uma vez que são parte de referência C ++ única. Ou você segue o caminho C ++ e usa o std::vectoroustd::array classe para sua finalidade.

Quanto à parte editada: Embora structseja um elemento de C, você define funções de construtor e operador, que o tornam um C ++ class. Consequentemente, o seu structnão seria compilado em C puro!

Thargon
fonte
Onde você quer chegar? std::vectorou std::arraytambém não pode conter referências. O padrão C ++ é bastante explícito que nenhum contêiner pode.
Sublinhado_