resolvidos - Está passando um objeto C++ para seu próprio construtor legal?




programação orientada a objetos c++ exercicios resolvidos (2)

Este não é um comportamento indefinido. Embora o foo não seja inicializado, você o está usando de uma maneira permitida pelo padrão. Depois que o espaço é alocado para um objeto, mas antes de ser totalmente inicializado, você tem permissão para usá-lo de formas limitadas. Ambas vinculando uma referência a essa variável e tendo seu endereço são permitidas.

Isso é coberto pelo relatório de defeitos 363: Inicialização da classe do self que diz:

E se sim, qual é a semântica da auto-inicialização da UDT? Por exemplo

 #include <stdio.h>

 struct A {
        A()           { printf("A::A() %p\n",            this);     }
        A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }
        ~A()          { printf("A::~A() %p\n",           this);     }
 };

 int main()
 {
  A a=a;
 }

pode ser compilado e imprime:

A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8

e a resolução foi:

3.8 [basic.life] o parágrafo 6 indica que as referências aqui são válidas. É permitido obter o endereço de um objeto de classe antes de ser totalmente inicializado, e é permitido passá-lo como um argumento para um parâmetro de referência, desde que a referência possa ser vinculada diretamente. Exceto pela falha em converter os ponteiros em void * para o% p nos printfs, esses exemplos são padronizados.

A citação completa da seção 3.8 [basic.life] do rascunho da norma C ++ 14 é a seguinte:

Da mesma forma, antes que o tempo de vida de um objeto tenha sido iniciado, mas após o armazenamento que o objeto ocupará tenha sido alocado ou, após o tempo de vida de um objeto ter terminado e antes do armazenamento que o objeto ocupado é reutilizado ou liberado, qualquer glvalue o objeto original pode ser usado, mas apenas de formas limitadas. Para um objeto em construção ou destruição, consulte 12.7. Caso contrário, tal glvalue refere-se ao armazenamento alocado (3.7.4.2), e usar as propriedades do glvalue que não dependem de seu valor é bem definido. O programa tem comportamento indefinido se:

  • uma conversão de lvalor para rvalue (4.1) é aplicada a um tal valor,

  • o glvalue é usado para acessar um membro de dados não estático ou chamar uma função de membro não-estático do objeto, ou

  • o glvalue está ligado a uma referência a uma classe base virtual (8.5.3), ou

  • o glvalue é usado como o operando de um dynamic_cast (5.2.7) ou como o operando do typeid.

Não estamos fazendo nada com foo que se enquadre no comportamento indefinido, conforme definido pelos marcadores acima.

Se tentarmos isso com Clang, vemos um aviso ameaçador ( veja ao vivo ):

aviso: a variável 'foo' é não inicializada quando usada dentro de sua própria inicialização [-Wininitialized]

É um aviso válido, pois produzir um valor indeterminado a partir de uma variável automática não inicializada é um comportamento indefinido . No entanto, neste caso você está apenas ligando uma referência e obtendo o endereço da variável dentro do construtor, que não produz um valor indeterminado e é válido. Por outro lado, o seguinte exemplo de auto-inicialização do padrão C ++ 11 :

int x = x ;

invoca comportamento indefinido.

Problema ativo 453: Referências só podem se ligar a objetos “válidos” também parece relevante, mas ainda está aberto. A linguagem proposta inicial é consistente com o Relatório de Defeito 363.

Estou surpreso ao descobrir acidentalmente que os seguintes trabalhos:

#include <iostream>            
int main(int argc, char** argv)
{
  struct Foo {
    Foo(Foo& bar) {
      std::cout << &bar << std::endl;
    }
  };
  Foo foo(foo); // I can't believe this works...
  std::cout << &foo << std::endl; // but it does...
}

Eu estou passando o endereço do objeto construído em seu próprio construtor. Isso parece uma definição circular no nível da fonte. Os padrões realmente permitem que você passe um objeto para uma função antes que o objeto seja construído ou esse comportamento é indefinido?

Eu suponho que não é tão estranho, dado que todas as funções de membro de classe já têm um ponteiro para os dados para sua instância de classe como um parâmetro implícito. E o layout dos membros de dados é fixado em tempo de compilação.

Note, não estou perguntando se isso é útil ou uma boa ideia; Eu estou apenas remexendo para aprender mais sobre as aulas.


O construtor é chamado em um ponto em que a memória é alocada para o objeto a ser. Nesse ponto, nenhum objeto existe nesse local (ou possivelmente um objeto com um destruidor trivial). Além disso, o ponteiro this refere-se a essa memória e a memória está adequadamente alinhada.

Como a memória está alocada e alinhada, podemos nos referir a ela usando expressões lvalue do tipo Foo (ou seja, Foo& ). O que ainda não podemos fazer é ter uma conversão lvalue para rvalue. Isso é permitido somente depois que o corpo do construtor é inserido.

Neste caso, o código apenas tenta imprimir &bar dentro do corpo do construtor. Seria até legal imprimir o bar.member aqui. Desde que o corpo do construtor foi inserido, existe um objeto Foo e seus membros podem ser lidos.

Isso nos deixa com um pequeno detalhe, e essa é a pesquisa de nome. Em Foo foo(foo) , o primeiro foo introduz o nome no escopo e o segundo foo portanto, faz referência ao nome recém-declarado. É por isso que int x = x é inválido, mas int x = sizeof(x) é válido.





undefined-behavior