Endereço da subclasse igual ao endereço da classe base virtual?

8

Todos sabemos que, ao usar uma herança simples e simples, o endereço de uma classe derivada é o mesmo que o endereço da classe base. A herança múltipla torna isso falso.

A herança virtual também torna isso falso? Em outras palavras, o código a seguir está correto:

struct A {};

struct B : virtual A
{
    int i;
};

int main()
{
    A* a = new B; // implicit upcast
    B* b = reinterpret_cast<B*>(a); // fishy?
    b->i = 0;

    return 0;
}
user1610015
fonte
1
reinterpret_castcom classes é sempre suspeito (exceto da classe para void*e volta para a mesma classe).
hyde
3
"Todos sabemos que, ao usar uma herança simples e simples, o endereço de uma classe derivada é o mesmo que o endereço da classe base" é uma afirmação bastante forte. Tem certeza de que o padrão garante isso?
hyde
7
É uma peculiaridade interessante das línguas humanas que nenhuma frase que comece com "todos sabemos que" é verdadeira.
molbdnilo
5
"O C ++ seria uma linguagem bastante impraticável se apenas contássemos com o que o padrão garante". Não estou desenvolvendo o C ++ há décadas como outros, mas nunca tive que violar o padrão ou confiar em comportamentos não especificados / indefinidos em meus aplicativos.
Timo
2
@ user1610015 Sua primeira frase nem sempre é verdadeira com alguns dos principais compiladores e não é assim pelo padrão C ++, portanto deve haver alguma outra especificação (sobre um compilador específico ou ABI) que a garanta para seu caso particular.
Öö Tiib

Respostas:

5

Todos sabemos que, ao usar uma herança simples e simples, o endereço de uma classe derivada é o mesmo que o endereço da classe base.

Eu acho que a afirmação não é verdadeira. No código abaixo, temos uma herança simples (não virtual) simples (não múltipla), mas os endereços são diferentes.

class A
{
public:
   int getX()
   {
      return 0;
   }
};

class B : public A
{
public:
   virtual int getY()
   {
      return 0;
   }
};

int main()
{
   B b;
   B* pB = &b;

   //A* pA = dynamic_cast<A*>(pB);
   A* pA = static_cast<A*>(pB);

   std::cout << "The address of pA is: " << pA << std::endl;
   std::cout << "The address of pB is: " << pB << std::endl;

   return 0;
}

e a saída para o VS2015 é:

The address of pA is: 006FF8F0
The address of pB is: 006FF8EC

A herança virtual também torna isso falso?

Se você alterar a herança no código acima para virtual, o resultado será o mesmo. portanto, mesmo no caso de herança virtual, os endereços dos objetos base e derivados podem ser diferentes.

Gupta
fonte
Na verdade, o g ++ também confirma seu caso com o código modificado por bits: coliru.stacked-crooked.com/a/ccea741b7126ee8a
Öö Tiib
O @ ÖöTiib void main()é aceitável mesmo em compiladores modernos do MSVS. BTW, obrigado pelo comentário. Eu atualizei o código.
Gupta
1
Não, void main()não é aceitável. Tem que estar de int main()acordo com o padrão. E remova isso dynamic_castdo código, não é necessário lá e causa confusão.
geza
2

O resultado de reinterpret_cast<B*>(a);é garantido apenas para apontar para o Bobjeto anexoa se o asubobjeto e o Bobjeto anexo forem interconversíveis por ponteiro , consulte [expr.static.cast] / 3 do padrão C ++ 17.

O objeto de classe derivada é interconversível com ponteiro com o objeto de classe base apenas se o objeto derivado for de layout padrão , não tiver membros de dados não estáticos diretos e o objeto de classe base for seu primeiro subobjeto de classe base. [base.com]] /4,3

Ter uma virtualclasse base desqualifica uma classe de ser layout padrão . [classe] /7.2 .

Portanto, como Bpossui uma classe base virtual e um membro de dados não estático, bnão apontará para o Bobjeto envolvente , mas, em vez disso,b , o valor do ponteiro permanecerá inalterado a.

Acessando o imembro como se estivesse apontando para oB objeto tem um comportamento indefinido.

Quaisquer outras garantias viriam da sua ABI específica ou de outra especificação.

noz
fonte
2

A herança múltipla torna isso falso.

Isso não está totalmente correto. Considere este exemplo:

struct A {};
struct B : A {};
struct C : A {};
struct D : B, C {};

Ao criar uma instância de D, Be Csão instanciados, cada um com sua respectiva instância de A. No entanto, não haveria problema se a instância de Dtivesse o mesmo endereço de sua instância de Be sua respectiva instância de A. Embora não seja necessário, é exatamente isso que acontece ao compilar com clang 11e gcc 10:

D: 0x7fffe08b4758 // address of instance of D
B: 0x7fffe08b4758 and A: 0x7fffe08b4758 // same address for B and A
C: 0x7fffe08b4760 and A: 0x7fffe08b4760 // other address for C and A

A herança virtual também torna isso falso

Vamos considerar uma versão modificada do exemplo acima:

struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {};

O uso do virtualespecificador de função geralmente é usado para evitar chamadas de função ambíguas. Portanto, ao usar virtualherança, as instâncias Be Cdevem criar uma Ainstância comum . Ao instanciar D, obtemos os seguintes endereços:

D: 0x7ffc164eefd0
B: 0x7ffc164eefd0 and A: 0x7ffc164eefd0 // again, address of A and B = address of D
C: 0x7ffc164eefd8 and A: 0x7ffc164eefd0 // A has the same address as before (common instance)

O código a seguir está correto

Aqui não há razão para usar reinterpret_cast, ainda mais, isso resulta em um comportamento indefinido. Use em static_castvez disso:

A* pA = static_cast<A*>(pB);

Ambos os modelos se comportam de maneira diferente neste exemplo. O reinterpret_castreinterpretará pBcomo um ponteiro para A, mas o ponteiro pApode apontar para um endereço diferente, como no exemplo acima (C vs A). O ponteiro será convertido corretamente se você usar static_cast.

mfnx
fonte
-2

A razão ae bsão diferentes no seu caso é porque, como Anão possui nenhum método virtual, Anão está mantendo a vtable. Por outro lado, Bmantém umvtable .

Quando você faz upcast A, o compilador é inteligente o suficiente para ignorar o vtablesignificado B. E, portanto, a diferença de endereços. Você não deveria reinterpret_castvoltar B, não funcionaria.

Para verificar minha reivindicação, tente adicionar um virtualmétodo, digamos virtual void foo() {}em class A. Agora Atambém manterá um vtable. Portanto, downcast ( reinterpret_cast) para B retornará o original b.

theWiseBro
fonte
vtables não são relevantes aqui.
mfnx
A pergunta do @mfnx OP Subclass address equal to virtual base class address? tem tudo a ver com herança virtual. E herança virtual tem tudo a ver com vtables.
theWiseBro
O exemplo do @walnut OP realiza uma herança virtual. A razão pela qual a transmissão dará resultado errado é devido à ausência da tabela v na classe A. É verdade que isso não deve ser feito e é ilegal, mas vamos ser práticos.
theWiseBro
@geza Sim, desculpe pelo meu comentário estúpido. Ainda assim, duvido que a adição de métodos virtuais Agaranta que os endereços correspondam e o elenco funcione, na teoria ou na prática. Não consigo remover meu voto negativo sem uma edição posterior, porque ele foi bloqueado.
noz
"O motivo aeb são diferentes em ...": eles podem compartilhar o mesmo endereço. Ter uma tabela v ou não, não significa que você terá os mesmos endereços. Observe as diferenças que obtive com clang e gcc em comparação com @gupta no VS2015.
mfnx