Declaração de encaminhamento de tipos / classes aninhados em C ++

197

Recentemente, fiquei preso em uma situação como esta:

class A
{
public:
    typedef struct/class {...} B;
...
    C::D *someField;
}

class C
{
public:
    typedef struct/class {...} D;
...
    A::B *someField;
}

Geralmente você pode declarar um nome de classe:

class A;

Mas você não pode encaminhar declarar um tipo aninhado, o seguinte causa erro de compilação.

class C::D;

Alguma ideia?

Calmarius
fonte
6
Por que você precisa daquilo? Observe que você pode encaminhar declarar se é um membro da mesma classe que está sendo definida: class X {class Y; Y * a; }; classe X :: Y {};
Johannes Schaub - litb 4/06/09
Esta solução funcionou para mim (namespace C {classe D;};): stackoverflow.com/questions/22389784/...
Albert Wiersch
Eu encontrei um link de
bitlixi

Respostas:

224

Você não pode fazer isso, é um buraco na linguagem C ++. Você precisará cancelar o aninhamento de pelo menos uma das classes aninhadas.

Adam Rosenfield
fonte
6
Obrigado pela resposta. No meu caso, elas não são minhas classes aninhadas. Eu esperava evitar uma enorme dependência do arquivo de cabeçalho da biblioteca com uma pequena referência para a frente. Gostaria de saber se C ++ 11 corrigiu?
Marsh Ray
61
Oh Exatamente o que eu não queria que o google aparecesse. Obrigado de qualquer maneira pela resposta concisa.
precisa saber é o seguinte
19
O mesmo aqui ... alguém sabe por que não é possível? Parece que há casos de uso válidos e essa falta impede a consistência da arquitetura em algumas situações.
Maël Nison
Você pode usar amigo. E basta colocar um comentário em que você o está usando para solucionar o problema no C ++.
Erik Aronesty
3
Sempre que eu encontrar essas falhas desnecessárias nesta língua ersatz, estou dividido entre rir e chorar
SongWithoutWords
33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

Eu precisava de uma referência direta como:

class IDontControl::Nested; // But this doesn't work.

Minha solução alternativa foi:

class IDontControl_Nested; // Forward reference to distinct name.

Mais tarde, quando eu poderia usar a definição completa:

#include <idontcontrol.h>

// I defined the forward ref like this:
class IDontControl_Nested : public IDontControl::Nested
{
    // Needed to make a forwarding constructor here
    IDontControl_Nested(int i) : Nested(i) { }
};

Essa técnica provavelmente seria mais problemática do que vale a pena se houvesse construtores complicados ou outras funções especiais de membros que não fossem herdadas sem problemas. Eu poderia imaginar certas mágicas reagindo mal.

Mas, no meu caso muito simples, parece funcionar.

Marsh Ray
fonte
16
No C ++ 11, você pode herdar construtores using basename::basename;na classe derivada, portanto, não há problema com ctors complicados.
Xeo 7/11
1
Bom truque, mas não funcionará se o ponteiro para IDontControl :: Nested for usado no mesmo cabeçalho (onde foi declarado adiante) e acessado a partir de código externo que também inclui a definição completa de IDontControl. (Como o compilador não corresponderá a IDontControl_Nested e IDontControl :: Nested). Solução alternativa é executar conversão estática.
Artem Pisarenko
Eu recomendo fazer o oposto e ter o lado de fora da classe, e usar apenas typedefdentro da classe
ridderhoff
3

Se você realmente deseja evitar # incluir o arquivo de cabeçalho desagradável no seu arquivo de cabeçalho, faça o seguinte:

arquivo hpp:

class MyClass
{
public:
    template<typename ThrowAway>
    void doesStuff();
};

arquivo cpp

#include "MyClass.hpp"
#include "Annoying-3rd-party.hpp"

template<> void MyClass::doesStuff<This::Is::An::Embedded::Type>()
{
    // ...
}

Mas então:

  1. você precisará especificar o tipo incorporado no momento da chamada (especialmente se sua função não aceitar nenhum parâmetro do tipo incorporado)
  2. sua função não pode ser virtual (porque é um modelo)

Então, sim, trocas ...

Edenbridge
fonte
1
Que diabos é um hpparquivo?
Naftali tcp Neal
7
lol, um arquivo de cabeçalho .hpp é usado em projetos C ++ para distingui-lo de um arquivo de cabeçalho C que normalmente termina com .h. Ao trabalhar com C ++ e C no mesmo projeto, algumas pessoas preferem arquivos .hpp e .cpp para arquivos C ++, para torná-lo explicitamente com que tipo de arquivos eles estão lidando e arquivos .h e .c para arquivos C.
Bitek
2

Isso pode ser feito declarando a classe externa como um espaço para nome .

Exemplo: Temos que usar uma classe aninhada others :: A :: Nested in others_a.h, que está fora de nosso controle.

others_a.h

namespace others {
struct A {
    struct Nested {
        Nested(int i) :i(i) {}
        int i{};
        void print() const { std::cout << i << std::endl; }
    };
};
}

my_class.h

#ifndef MY_CLASS_CPP
// A is actually a class
namespace others { namespace A { class Nested; } }
#endif

class MyClass {
public:
    MyClass(int i);
    ~MyClass();
    void print() const;
private:
    std::unique_ptr<others::A::Nested> _aNested;
};

my_class.cpp

#include "others_a.h"
#define MY_CLASS_CPP // Must before include my_class.h
#include "my_class.h"

MyClass::MyClass(int i) :
    _aNested(std::make_unique<others::A::Nested>(i)) {}
MyClass::~MyClass() {}
void MyClass::print() const {
    _aNested->print();
}
bitlixi
fonte
1
Pode funcionar, mas não está documentado. A razão pela qual ele funciona é que a::bé confundido da mesma maneira, não importa se aé classe ou espaço para nome.
jaskmar
3
Não funciona com Clang ou GCC. Ele diz que a classe externa foi declarada como algo diferente de um espaço para nome.
Dugi
1

Eu não chamaria isso de resposta, mas, mesmo assim, uma descoberta interessante: se você repetir a declaração de sua estrutura em um espaço para nome chamado C, está tudo bem (pelo menos no gcc). Quando a definição de classe de C é encontrada, ela substitui silenciosamente o espaço de nome C.

namespace C {
    typedef struct {} D;
}

class A
{
public:
 typedef struct/class {...} B;
...
C::D *someField;
}

class C
{
public:
   typedef struct/class {...} D;
...
   A::B *someField;
}
nschmidt
fonte
1
Eu tentei isso com o cygwin gcc e ele não compila se você tentar fazer referência ao A.someField. C :: D na definição de classe A, na verdade, refere-se à estrutura (vazio) no espaço de nomes, não a estrutura na classe C (entre este não compila em MSVC)
Dolphin
Ele fornece o erro: "'classe C' redeclarada como um tipo diferente de símbolo"
Calmarius 04/06/2009
9
Parece um bug do GCC. Parece que um nome de espaço para nome pode ocultar um nome de classe no mesmo escopo.
Johannes Schaub - litb 4/06/09
0

Isso seria uma solução alternativa (pelo menos para o problema descrito na pergunta - não para o problema real, ou seja, quando não houver controle sobre a definição de C):

class C_base {
public:
    class D { }; // definition of C::D
    // can also just be forward declared, if it needs members of A or A::B
};
class A {
public:
    class B { };
    C_base::D *someField; // need to call it C_base::D here
};
class C : public C_base { // inherits C_base::D
public:
    // Danger: Do not redeclare class D here!!
    // Depending on your compiler flags, you may not even get a warning
    // class D { };
    A::B *someField;
};

int main() {
    A a;
    C::D * test = a.someField; // here it can be called C::D
}
chtz
fonte
0

Se você tiver acesso para alterar o código fonte das classes C e D, poderá retirar a classe D separadamente e inserir um sinônimo para ela na classe C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
Suvorov Ivan
fonte