Padrão semântica passados ??por referência em C ++ [fechado]
-
02-07-2019 - |
Pergunta
EDIT: Esta pergunta é mais sobre engenharia da língua de C ++ em si. Eu usei C ++ como um exemplo para mostrar o que eu queria, principalmente porque eu usá-lo diariamente. Eu não quero saber como ele funciona em C ++, mas abrir uma discussão sobre como ele pode ser feito.
Essa não é a forma como ele funciona agora, essa é a maneira que eu desejo isso poderia ser feito, e que iria quebrar C compability com certeza, mas isso é o que eu penso externo "C" tem tudo a ver .
Quer dizer, em cada função ou método que você declare agora você tem que escrever explícito que o objeto será enviado por referência prefixar o operador de referência sobre ele. Desejo que cada tipo não-POD seriam enviados automaticamente por referência, porque eu uso muito isso, na verdade, para cada objeto que é mais de 32 bits de tamanho, e isso é quase todas as classes da mina.
Vamos exemplificar como é agora, assumir a , b e c ser classes:
class example { public: int just_use_a(const a &object); int use_and_mess_with_b(b &object); void do_nothing_on_c(c object); };
Agora, o que eu desejo:
class example { public: int just_use_a(const a object); int use_and_mess_with_b(b object); extern "C" void do_nothing_on_c(c object); };
Agora, do_nothing_on_c () poderia se comportar exatamente como é hoje.
Isso seria interessante, pelo menos para mim, se sente muito mais clara, e também se você sei todos os parâmetros não-POD está vindo por referência eu acredito que os erros seria o mesmo que se você tivesse para declarar explicitamente-lo.
Um outro ponto de vista para esta mudança, de alguém vindo de C, o operador de referência parece-me uma maneira de obter a variável endereço , essa é a maneira que eu usei para obter ponteiros. Quero dizer, ele é o mesmo operador, mas com diferente semântica em diferentes contextos, não que me sinto um pouco errado pouco para você também?
Solução
Eu acho que você está perdendo o ponto de ++ semântica C e C ++. Você perdeu o fato de C ++ está correto na passagem (quase) tudo por valor, porque é a forma como é feito em C. Sempre . Mas não só em C, como eu vou mostrar-lhe a seguir ...
Parâmetros Semântica no C
Em C, tudo é passado por valor. "primitivos" e "pods" são passados ??por copiar seu valor. Modificá-los em sua função, eo original não será modificado. Ainda assim, o custo de copiar algumas PODs poderia ser não-trivial.
Quando você usa a notação de ponteiro (a *), você não está passando por referência. Você está passando uma cópia do endereço. Que é mais ou menos o mesmo, apenas com uma diferença sutil:
typedef struct { int value ; } P ;
/* p is a pointer to P */
void doSomethingElse(P * p)
{
p->value = 32 ;
p = malloc(sizeof(P)) ; /* Don't bother with the leak */
p->value = 45 ;
}
void doSomething()
{
P * p = malloc(sizeof(P)) ;
p->value = 25 ;
doSomethingElse(p) ;
int i = p->value ;
/* Value of p ? 25 ? 32 ? 42 ? */
}
O valor final de p> valor é 32. Porque p foi aprovada, copiando o valor do endereço. Assim, o p original não foi modificado (eo novo vazou).
Parâmetros Semântica em Java e C sharp
Pode ser surpreendente para alguns, mas em Java, tudo é copiado por valor, também. O exemplo C acima daria exatamente os mesmos resultados em Java. Isso é quase o que quiser, mas você não seria capaz de passar primitiva "por referência / ponteiro" tão facilmente como no C.
Em C #, eles adicionaram a palavra-chave "ref". Ele funciona mais ou menos como a referência em C ++. O ponto é, no C #, você tem que mencioná-lo tanto na declaração da função, e em cada chamada. Eu acho que isso não é o que você quer, mais uma vez.
Parâmetros Semântica em C ++
Em C ++, quase tudo é passado, copiando o valor. Quando você estiver usando nada, mas o tipo de símbolo, você está copiando o símbolo (como é feito em C). É por isso que, quando você está usando o *, você está passando uma cópia do endereço do símbolo.
Mas quando você está usando o &, em seguida, assumir que você está passando o objeto real (seja ele struct, int, ponteiro, qualquer que seja):. A ??referência
É fácil confundir-lo como açúcar syntaxic (ou seja, nos bastidores, ele funciona como um ponteiro, e o código gerado é o mesmo usado para um ponteiro). Mas ...
A verdade é que a referência é mais do que o açúcar syntaxic.
- ponteiros Ao contrário, autoriza manipular o objeto como se na pilha.
- ponteiros unline, quando associatied com a palavra chave const, autoriza promoção implícita de um tipo para outro (por meio de construtores, principalmente).
- ponteiros Ao contrário, o símbolo não é suposto ser NULL / inválido.
- Ao contrário da "by-cópia", você não está gastando tempo inútil copiar o objeto
- Ao contrário da "by-cópia", você pode usá-lo como um [out] parâmetro
- Ao contrário da "by-cópia", você pode usar toda a gama de OOP em C ++ (ou seja, você passar um objeto completo para uma função esperando uma interface).
Assim, referências tem o melhor dos dois mundos.
Vamos ver o exemplo C, mas com uma variação C ++ sobre a função doSomethingElse:
struct P { int value ; } ;
// p is a reference to a pointer to P
void doSomethingElse(P * & p)
{
p->value = 32 ;
p = (P *) malloc(sizeof(P)) ; // Don't bother with the leak
p->value = 45 ;
}
void doSomething()
{
P * p = (P *) malloc(sizeof(P)) ;
p->value = 25 ;
doSomethingElse(p) ;
int i = p->value ;
// Value of p ? 25 ? 32 ? 42 ?
}
O resultado é 42, eo velho p vazou, substituído pelo novo p. Porque, ao contrário de código C, não estamos passando uma cópia do ponteiro, mas a referência para o ponteiro, ou seja, o ponteiro em si.
Ao trabalhar com C ++, o exemplo acima deve ser cristal claro. Se não for, então você está faltando alguma coisa.
Conclusão
C ++ é passagem por cópia / valor porque é a forma como tudo funciona, seja em C, em C # ou em Java (mesmo em JavaScript ... :-P ...). E como C #, C ++ tem um operador de referência / palavra-chave, como um bônus .
Agora, tanto quanto eu entendo, você está, talvez, fazer o que eu chamo de meio-jockingly C + , ou seja, C com algumas características limitada C ++.
Talvez a sua solução é usar typedefs (que vai enfurecer o seu C ++ colegas, no entanto, para ver o código poluído por typedefs inúteis ...), mas fazendo isso só vai ofuscar o fato de que você está realmente faltando C ++ lá.
Como disse em outro post, você deve mudar a sua mentalidade de desenvolvimento C (de qualquer) para C ++ Desenvolvimento, ou talvez você deve mover-se para outro idioma. Mas não manter a programação do caminho C com características C ++, porque conscientemente ignorar / ofuscar o poder dos idiomas que você usa, você vai produzir código abaixo do ideal.
Nota: E não passar por cópia mais nada do que primitivos. Você vai castrar sua função de sua capacidade de OO, e em C ++, isso não é o que você quer.
Editar
A questão era um pouco modificado (ver https://stackoverflow.com/revisions/ 146271 / list ). Eu deixei a minha resposta original, e responder às novas perguntas abaixo.
O que você pensa sobre a passagem por referência semântica padrão em C ++? Como você disse, ele iria quebrar a compatibilidade, e você terá passagem por diferentes para primitivas (ie tipos built-in , o que ainda seria passado por cópia) e estruturas / objectos (que seriam passados ??como referências). Você teria que adicionar outro operador no sentido de "passar por valor" (o externo "C" é bastante horrível e já utilizado para outra coisa completamente diferente). Não, eu realmente gosto do jeito que é feito hoje em C ++.
[...] o operador de referência parece-me uma maneira de obter o endereço variável, essa é a maneira que eu usei para obter ponteiros. Quero dizer, ele é o mesmo operador, mas com diferente semântica em diferentes contextos, não que me sinto um pouco errado pouco para você também? Sim e não. Operador >> mudou sua semântica quando usado com fluxos C ++, também. Então, você pode usar o operador + = para substituir strcat. Eu acho que o operador & acostumei porque sua significação como "oposto do ponteiro", e porque não queria usar mais um símbolo (ASCII é limitado, eo operador de escopo :: bem como ponteiro -> mostra que alguns outros símbolos são utilizáveis). Mas agora, se & incomoda, && realmente vai enervar você, como eles adicionaram um unário && em C ++ 0x (uma espécie de super-referência ...). Eu ainda tenho que digerir isso sozinho ...
Outras dicas
A opção de compilador que totalmente muda o significado de uma seção de sons código como uma péssima idéia para mim. Quer começar a usar o C ++ sintaxe ou encontrar um idioma diferente.
Eu prefiro não abusar referências mais, fazendo cada (não qualificado) parâmetro uma referência.
As principais referências razão foram adicionados ao C ++ WAS a operador sobrecarga suporte ; se você quiser "pass-by-reference" semântica, C tinha uma maneira perfeitamente razoável de fazê-lo:. ponteiros
Usando ponteiros deixa claro sua intenção de mudar o valor do objeto pontiagudo, e é possível ver isso por apenas olhando para a chamada de função, você não tem que olhar para a declaração função para ver se ele está usando um referência.
Além disso, veja
Eu quero mudar o argumento, Devo usar um ponteiro ou devo usar uma referência? Eu não sei uma forte razão lógica. Se passar `` não é uma objeto '' (por exemplo, um ponteiro nulo) é aceitável, usando um ponteiro faz sentido. Meu estilo pessoal é usar um ponteiro quando eu quiser modificar uma objeto porque, em alguns contextos que faz com que seja mais fácil de detectar que uma modificação é possível.
Sim, eu sou da opinião de que essa é uma sobrecarga muito confuso.
Este é o que a Microsoft tem a dizer sobre a situação:
Não declarações de referência confunda com o uso do endereço-de operador. Quando & identificador é precedido por um tipo, tais como int ou carvão, em seguida, o identificador é declarada como uma referência para o tipo. Quando o & identificador não é precedida por um tipo, o uso é a do endereço-de operador.
Eu não sou realmente grande em C ou C ++, mas eu recebo dores de cabeça maiores classificando para fora os vários usos do * e & em ambas as línguas que eu codificação em assembler.
O melhor conselho é fazer um hábito de pensar sobre o que você realmente quer que aconteça. Passagem por referência é bom quando você não tem um construtor de cópia (ou não quiser usá-lo) e é mais barato para objetos grandes. No entanto, em seguida, mutações no parâmetro são senti fora da classe. Você poderia, em vez passar por referência const
- então não há mutações, mas você não pode fazer modificações locais. Passe const por valor para objetos baratos que devem ser lidas somente na função e passam não-const por valor quando você quiser uma cópia que você pode fazer modificações locais para.
Cada permutação (por valor / por referência e const / não-const) tem diferenças importantes que definitivamente não são equivalentes.
Quando você passar por valor, você está copiando dados para a pilha. No caso em que você tem um operador = definido para a estrutura ou classe que você está passando, ele é executado. Não existe qualquer directiva compilador Estou ciente de que iria lavar a ladainha de confusão linguagem implícita de que a mudança proposta seria inerentemente causar.
A melhor prática comum é passar valores por referência const, não apenas por referência. Isso garante que o valor não pode ser alterado na função de chamada. Este é um elemento de uma base de código const-correta.
A base de código totalmente const-correta vai ainda mais longe, acrescentando const para o final de protótipos. Considere o seguinte:
void Foo::PrintStats( void ) const {
/* Cannot modify Foo member variables */
}
void Foo::ChangeStats( void ) {
/* Can modify foo member variables */
}
Se você estava a passar um objeto Foo-se para uma função, com o prefixo const, você é capaz de chamar printStats (). O compilador erro fora em uma chamada para ChangeStats ().
void ManipulateFoo( const Foo &foo )
{
foo.PrintStats(); // Works
foo.ChangeStats(); // Oops; compile error
}
Eu honestamente acho que toda esta passagem por valor / passar pela ideia de referência em C ++ é enganosa. Tudo é passar por valor. Você tem três casos:
-
Onde você passar uma cópia local de uma variável
void myFunct(int cantChangeMyValue)
-
Onde você passar uma cópia local de um ponteiro para uma variável
void myFunct(int* cantChangeMyAddress) { *cantChangeMyAddress = 10; }
-
Onde você passar uma 'referência', mas através da magia do compilador é como se você passou um ponteiro e simplesmente desreferenciado-lo cada vez.
void myFunct(int & hereBeMagic) { hereBeMagic = 10; // same as 2, without the dereference }
Eu, pessoalmente encontrá-lo muito menos confuso para lembrar que tudo é passar por valor. Em alguns casos, esse valor pode ser um endereço, que lhe permite mudar as coisas fora da função.
O que você está sugerindo não permitiria que o programador para fazer número 1. Eu, pessoalmente, acho que seria uma má idéia para tirar essa opção. Uma grande vantagem de C / C ++ é ter uma gestão de memória de granulação fina. Fazendo tudo passagem por referência é simplesmente tentando fazer C ++ mais como Java.
há algo não está claro. quando você diz:
int b (b & param);
o que você pretende para o segundo 'b'? esqueceu-se de introduzir um tipo? esqueceu-se de escrever de forma diferente em relação ao primeiro 'b'? Não acha que é claro para escrever algo como:
class B{/*something...*/};
int b(B& param);
Desde agora, eu suponho que você quer dizer o que eu escrevo.
Agora, sua pergunta é "você não acha que vai ser melhor que o compilador irá considerar cada passagem por valor de um não-POD como pass-by-ref?". O primeiro problema é que ele vai quebrou seu contrato. Eu suponho que você quer dizer passagem por CONST-referência, e não apenas por referência.
A sua questão agora é reduzida a este: "você sabe se há alguns compiladores directiva, que chamada de função pode otimizar por valor"
A resposta agora é "eu não sei".
Eu acho que c ++ se tornar muito confuso se você começar a misturar todo o tipo de parâmetros disponíveis, com suas variações const.
Ele fica rapidamente fora de mão para trak todas as chamadas construtores de cópia, todas as dereferences sobrecarregado e assim por diante.