Pergunta

Sou novo em programação C++, mas tenho experiência em Java.Preciso de orientação sobre como passar objetos para funções em C++.

Preciso passar ponteiros, referências ou valores que não são ponteiros e não referências?Lembro que em Java não existem esses problemas, pois passamos apenas a variável que contém referência aos objetos.

Seria ótimo se você também pudesse explicar onde usar cada uma dessas opções.

Foi útil?

Solução

Regras prolongadas para C ++ 11:

Passar por valor, exceto quando

  1. Você não precisa de propriedade do objeto e um simples pseudônimo fará, nesse caso, você passar por const referência,
  2. você deve mudar o objeto, nesse caso, use passar por um nãoconst Referência de LValue,
  3. você passa objetos de classes derivadas como classes base, nesse caso, você precisa passe por referência. (Use as regras anteriores para determinar se deve passar por const referência ou não.)

A passagem pelo ponteiro é praticamente nunca recomendada. Os parâmetros opcionais são melhor expressos como um std::optional (boost::optional Para Libs DST mais antigos) e o alias é feito bem por referência.

A semântica de movimentos de C ++ 11 torna a passagem e o retorno pelo valor muito mais atraente, mesmo para objetos complexos.


Regras prolongadas para C ++ 03:

Aprovar argumentos por const referência, exceto quando

  1. Eles devem ser alterados dentro da função e essas mudanças devem ser refletidas fora, nesse caso você passar por nãoconst referência
  2. A função deve ser chamada sem nenhum argumento; nesse caso, você passa por ponteiro, para que os usuários possam passar NULL/0/nullptr em vez de; aplique a regra anterior para determinar se você deve passar por um ponteiro para um const argumento
  3. eles são de tipos internos, o que pode ser Passado por cópia
  4. eles devem ser alterados dentro da função e essas mudanças devem não ser refletido fora, nesse caso, você pode passe por cópia (Uma alternativa seria passar de acordo com as regras anteriores e fazer uma cópia dentro da função)

(Aqui, "Pass by Value" é chamado "Pass by Copy", porque a passagem pelo valor sempre cria uma cópia em C ++ 03)


Há mais isso, mas essas poucas regras deste iniciante levarão você muito longe.

Outras dicas

Existem algumas diferenças nas convenções de chamada em C++ e Java.Em C++, tecnicamente falando, existem apenas duas convenções:passagem por valor e passagem por referência, com alguma literatura incluindo uma terceira convenção de passagem por ponteiro (que na verdade é passagem por valor de um tipo de ponteiro).Além disso, você pode adicionar constância ao tipo do argumento, aprimorando a semântica.

Passe por referência

Passar por referência significa que a função receberá conceitualmente a instância do seu objeto e não uma cópia dele.A referência é conceitualmente um alias para o objeto que foi usado no contexto de chamada e não pode ser nula.Todas as operações realizadas dentro da função se aplicam ao objeto fora da função.Esta convenção não está disponível em Java ou C.

Passagem por valor (e passagem por ponteiro)

O compilador irá gerar uma cópia do objeto no contexto de chamada e usar essa cópia dentro da função.Todas as operações realizadas dentro da função são feitas na cópia, não no elemento externo.Esta é a convenção para tipos primitivos em Java.

Uma versão especial disso é passar um ponteiro (endereço do objeto) para uma função.A função recebe o ponteiro, e toda e qualquer operação aplicada ao próprio ponteiro é aplicada à cópia (ponteiro), por outro lado, as operações aplicadas ao ponteiro desreferenciado serão aplicadas à instância do objeto naquele local de memória, portanto a função pode ter efeitos colaterais.O efeito de usar a passagem por valor de um ponteiro para o objeto permitirá que a função interna modifique valores externos, como acontece com a passagem por referência e também permitirá valores opcionais (passar um ponteiro nulo).

Esta é a convenção usada em C quando uma função precisa modificar uma variável externa, e a convenção usada em Java com tipos de referência:a referência é copiada, mas o objeto referido é o mesmo:alterações na referência/ponteiro não são visíveis fora da função, mas alterações na memória apontada são.

Adicionando const à equação

Em C++ você pode atribuir constância a objetos ao definir variáveis, ponteiros e referências em diferentes níveis.Você pode declarar uma variável como constante, pode declarar uma referência a uma instância constante e pode definir todos os ponteiros para objetos constantes, ponteiros constantes para objetos mutáveis ​​e ponteiros constantes para elementos constantes.Por outro lado, em Java você só pode definir um nível de constância (palavra-chave final):o da variável (instância para tipos primitivos, referência para tipos de referência), mas você não pode definir uma referência a um elemento imutável (a menos que a própria classe seja imutável).

Isso é amplamente usado em convenções de chamada C++.Quando os objetos são pequenos você pode passar o objeto por valor.O compilador irá gerar uma cópia, mas essa cópia não é uma operação cara.Para qualquer outro tipo, se a função não alterar o objeto, você poderá passar uma referência a uma instância constante (geralmente chamada de referência constante) do tipo.Isso não copiará o objeto, mas o passará para a função.Mas ao mesmo tempo o compilador garantirá que o objeto não seja alterado dentro da função.

Regras de ouro

Estas são algumas regras básicas a seguir:

  • Prefira passagem por valor para tipos primitivos
  • Prefira passagem por referência com referências a constante para outros tipos
  • Se a função precisar modificar o argumento, use passagem por referência
  • Se o argumento for opcional, use passagem por ponteiro (para constante se o valor opcional não deve ser modificado)

Existem outros pequenos desvios destas regras, o primeiro dos quais é o tratamento da propriedade de um objeto.Quando um objeto é alocado dinamicamente com new, ele deve ser desalocado com delete (ou as [] versões dele).O objeto ou função responsável pela destruição do objeto é considerado o proprietário do recurso.Quando um objeto alocado dinamicamente é criado em um trecho de código, mas a propriedade é transferida para um elemento diferente, isso geralmente é feito com semântica de passagem por ponteiro ou, se possível, com ponteiros inteligentes.

Nota

É importante insistir na importância da diferença entre as referências C++ e Java.Em C++, as referências são conceitualmente a instância do objeto, não um acessador dele.O exemplo mais simples é implementar uma função swap:

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

A função de troca acima mudanças ambos os seus argumentos através do uso de referências.O código mais próximo em Java:

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

A versão Java do código modificará as cópias das referências internamente, mas não modificará os objetos reais externamente.As referências Java são ponteiros C sem aritmética de ponteiro que são passados ​​​​por valor para funções.

Existem vários casos a considerar.

Parâmetro modificado (parâmetros "out" e "in/out")

void modifies(T &param);
// vs
void modifies(T *param);

Este caso é principalmente sobre estilo:você quer que o código fique parecido ligar (obj) ou ligar(&obj)?No entanto, existem dois pontos em que a diferença é importante:o caso opcional, abaixo, e você deseja usar uma referência ao sobrecarregar operadores.

...e opcional

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

Parâmetro não modificado

void uses(T const &param);
// vs
void uses(T param);

Este é o caso interessante.A regra geral é que tipos "baratos para copiar" são passados ​​por valor — geralmente são tipos pequenos (mas nem sempre) — enquanto outros são passados ​​por const ref.No entanto, se você precisar fazer uma cópia dentro da sua função, você deve passar por valor.(Sim, isso expõe alguns detalhes de implementação. Este é o C++.)

...e opcional

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

Há aqui a menor diferença entre todas as situações, então escolha aquela que torna sua vida mais fácil.

Const por valor é um detalhe de implementação

void f(T);
void f(T const);

Estas declarações são, na verdade, as exatamente a mesma função! Ao passar por valor, const é puramente um detalhe de implementação. Experimente:

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types

Passe por valor:

void func (vector v)

Passe variáveis ​​por valor quando a função precisar de isolamento completo do ambiente, ou seja,para evitar que a função modifique a variável original, bem como para evitar que outros threads modifiquem seu valor enquanto a função está sendo executada.

A desvantagem são os ciclos de CPU e a memória extra gasta para copiar o objeto.

Passe por referência const:

void func (const vector& v);

Este formulário emula o comportamento de passagem por valor enquanto remove a sobrecarga de cópia.A função obtém acesso de leitura ao objeto original, mas não pode modificar seu valor.

A desvantagem é a segurança do thread:qualquer alteração feita no objeto original por outro thread aparecerá dentro da função enquanto ela ainda está em execução.

Passe por referência não const:

void func (vector& v)

Use isto quando a função tiver que escrever de volta algum valor na variável, que acabará sendo usado pelo chamador.

Assim como o caso de referência const, isso não é seguro para threads.

Passe pelo ponteiro const:

void func (const vector* vp);

Funcionalmente igual à passagem por referência const, exceto pela sintaxe diferente, além do fato de que a função de chamada pode passar o ponteiro NULL para indicar que não há dados válidos para passar.

Não é seguro para threads.

Passe por ponteiro não const:

void func (vector* vp);

Semelhante à referência não const.O chamador normalmente define a variável como NULL quando a função não deve escrever de volta um valor.Esta convenção é vista em muitas APIs da glibc.Exemplo:

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

Assim como todos passam por referência/ponteiro, não são seguros para threads.

Como ninguém mencionou que estou adicionando isso, quando você passa um objeto para uma função em c++, o construtor de cópia padrão do objeto é chamado se você não tiver um que crie um clone do objeto e depois o passe para o método, então quando você altera os valores do objeto que refletirão na cópia do objeto em vez do objeto original, esse é o problema em c++. Portanto, se você fizer com que todos os atributos da classe sejam ponteiros, os construtores de cópia copiarão os endereços do atributos de ponteiro, então quando o método invoca o objeto que manipula os valores armazenados nos endereços de atributos de ponteiro, as mudanças também refletem no objeto original que é passado como parâmetro, então este pode se comportar como um Java, mas não se esqueça que toda a sua classe os atributos devem ser ponteiros, você também deve alterar os valores dos ponteiros, ficará muito claro com a explicação do código.

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

Mas isso não é uma boa ideia, pois você acabará escrevendo muito código envolvendo ponteiros, que são propensos a vazamentos de memória e não se esqueça de chamar destruidores.E para evitar isso, o c++ possui construtores de cópia onde você criará uma nova memória quando os objetos contendo ponteiros forem passados ​​​​para argumentos de função que irão parar de manipular os dados de outros objetos, Java passa por valor e o valor é referência, portanto não requer construtores de cópia.

Existem três métodos de passar um objeto para uma função como um parâmetro:

  1. Passe por referência
  2. passar por valor
  3. adicionando constante no parâmetro

Passe pelo exemplo a seguir:

class Sample
{
public:
    int *ptr;
    int mVar;

    Sample(int i)
    {
        mVar = 4;
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }

    void PrintVal()
    {
        cout << "The value of the pointer is " << *ptr << endl
             << "The value of the variable is " << mVar;
   }
};

void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}


int main()
{

  Sample s1= 10;
  SomeFunc(s1);
  s1.PrintVal();
  char ch;
  cin >> ch;
}

Resultado:

Diga que estou em Somefunc
O valor do ponteiro é -17891602
O valor da variável é 4

A seguir, são apresentadas as maneiras de passar nos argumentos/parâmetros para funcionar no C ++.

1. por valor.

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

2. por referência.

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

3. por objeto.

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top