it-swarm-pt.tech

Encaminhar declarando um enum em C ++

Eu estou tentando fazer algo como o seguinte:

enum E;

void Foo(E e);

enum E {A, B, C};

que o compilador rejeita. Eu dei uma olhada rápida no Google e o consenso parece ser "você não pode fazer isso", mas não consigo entender por quê. Alguém pode explicar?

Esclarecimento 2: Estou fazendo isso porque tenho métodos privados em uma classe que recebe o enum e não quero que os valores do enum sejam expostos - portanto, por exemplo, não quero que ninguém saiba que E é definido como

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

como projeto X não é algo que eu quero que meus usuários saibam.

Então, eu queria encaminhar declarar o enum para que eu pudesse colocar os métodos privados no arquivo de cabeçalho, declarar o enum internamente no cpp e distribuir o arquivo de biblioteca e o cabeçalho para as pessoas.

Quanto ao compilador - é o GCC.

248
szevvy

A razão pela qual o enum não pode ser declarado para a frente é que, sem conhecer os valores, o compilador não pode saber o armazenamento necessário para a variável enum. Compilador C++ tem permissão para especificar o espaço de armazenamento real com base no tamanho necessário para conter todos os valores especificados. Se tudo o que é visível é a declaração de encaminhamento, a unidade de tradução não pode saber qual tamanho de armazenamento terá sido escolhido - pode ser um caractere ou um int ou outra coisa.


Da Seção 7.2.5 do Padrão ISO C++:

O tipo subjacente de uma enumeração é um tipo integral que pode representar todos os valores do enumerador definidos na enumeração. É implementação definida que tipo integral é usado como o tipo subjacente para uma enumeração, exceto que o tipo subjacente não deve ser maior que int, a menos que o valor de um enumerador não possa caber em um int ou unsigned int. Se a enumerator-list estiver vazia, o tipo subjacente é como se a enumeração tivesse um único enumerador com valor 0. O valor de sizeof() aplicado a uma enumeração type, um objeto do tipo enumeration ou um enumerador, é o valor de sizeof() aplicado ao tipo subjacente.

Como o chamador da função deve conhecer os tamanhos dos parâmetros para configurar corretamente a pilha de chamadas, o número de enumerações em uma lista de enumeração deve ser conhecido antes o protótipo da função.

Atualização: Em C++ 0X, uma sintaxe para os tipos de enum declarando para frente foi proposta e aceita. Você pode ver a proposta em http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf

201
KJAWolf

Encaminhar declaração de enums também é possível em C++ 0 x. Anteriormente, a razão pela qual os tipos de enumeração não podiam ser declarados para encaminhamento é porque o tamanho da enumeração depende de seu conteúdo. Contanto que o tamanho da enumeração seja especificado pelo aplicativo, ele pode ser declarado para a frente:

enum Enum1;                   //Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int;    //Legal in C++0x.
enum class Enum3;             //Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short;  //Illegal in C++0x, because Enum2 was previously declared with a different type.
187
user119017

Estou adicionando uma resposta atualizada aqui, considerando os desenvolvimentos recentes.

Você pode reencaminhar um enum em C++ 11, desde que declare o tipo de armazenamento ao mesmo tempo. A sintaxe é assim:

enum E : short;
void foo(E e);

....

enum E : short
{
    VALUE_1,
    VALUE_2,
    ....
}

Na verdade, se a função nunca se referir aos valores da enumeração, você não precisará da declaração completa nesse ponto.

Isto é suportado pelo G ++ 4.6 e adiante (-std=c++0x ou -std=c++11 em versões mais recentes). O Visual C++ 2013 suporta isso; nas versões anteriores, há algum tipo de suporte não padronizado que ainda não descobri - descobri que há uma sugestão de que uma declaração simples é legal, mas YMMV.

72
Tom

Encaminhar declarando coisas em C++ é muito útil porque dramaticamente acelera o tempo de compilação . Você pode encaminhar declarar várias coisas em C++, incluindo: struct, class, function, etc ...

Mas você pode encaminhar declarar um enum em C++?

Não você não pode.

Mas por que não permitir isso? Se fosse permitido, você poderia definir seu tipo enum em seu arquivo de cabeçalho, e seus valores enum em seu arquivo de origem. Parece que deveria ser permitido certo?

Errado.

Em C++ não há nenhum tipo padrão para enum como existe em C # (int). Em C++, seu tipo enum será determinado pelo compilador como qualquer tipo que se ajuste ao intervalo de valores que você possui para o seu enum.

O que isso significa?

Isso significa que o tipo subjacente do seu enum não pode ser totalmente determinado até que você tenha todos os valores de enum definidos. Que mans você não pode separar a declaração e definição do seu enum. E, portanto, você não pode encaminhar declarar um enum em C++.

O padrão ISO C++ S7.2.5:

O tipo subjacente de uma enumeração é um tipo integral que pode representar todos os valores enumeradores definidos na enumeração. É implementação definida que tipo integral é usado como o tipo subjacente para uma enumeração, exceto que o tipo subjacente não deve ser maior que int, a menos que o valor de um enumerador não possa caber em um int ou unsigned int. Se a lista de enumeradores estiver vazia, o tipo subjacente é como se a enumeração tivesse um único enumerador com valor 0. O valor de sizeof() aplicado a um tipo de enumeração, um objeto do tipo de enumeração ou um enumerador é o valor de sizeof() aplicado para o tipo subjacente.

Você pode determinar o tamanho de um tipo enumerado em C++ usando o operador sizeof. O tamanho do tipo enumerado é o tamanho do seu tipo subjacente. Desta forma, você pode adivinhar qual tipo seu compilador está usando para o seu enum.

E se você especificar o tipo do seu enum explicitamente assim:

enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);

Você pode encaminhar declarar seu enum?

Não. Mas por que não?

Especificar o tipo de um enum não é realmente parte do padrão C++ atual. É uma extensão do VC++. Será parte do C++ 0x embora.

fonte

30
Brian R. Bondy

[Minha resposta está errada, mas deixei aqui porque os comentários são úteis].

Encaminhar declarando enums é não-padrão, porque não é garantido que os ponteiros para diferentes tipos de enum tenham o mesmo tamanho. O compilador pode precisar ver a definição para saber quais ponteiros de tamanho podem ser usados ​​com esse tipo.

Na prática, pelo menos em todos os compiladores populares, os ponteiros para enums são um tamanho consistente. Encaminhar declaração de enums é fornecida como uma extensão de linguagem pelo Visual C++, por exemplo.

13
James Hopkin

Na verdade, não existe uma declaração antecipada de enum. Como a definição de um enum não contém nenhum código que possa depender de outro código usando o enum, geralmente não é um problema definir completamente o enum quando você o declara pela primeira vez.

Se o único uso de seu enum é por funções de membro particular, você pode implementar o encapsulamento por ter o próprio enum como um membro particular dessa classe. O enum ainda tem que ser totalmente definido no ponto de declaração, isto é, dentro da definição de classe. No entanto, isso não é um problema maior como declarar funções de membros privados lá, e não é uma exposição pior de internos de implementação do que isso.

Se você precisa de um grau mais profundo de ocultação para seus detalhes de implementação, você pode dividi-lo em uma interface abstrata, consistindo apenas de funções virtuais puras e uma classe concreta, completamente oculta, implementando (herdando) a interface. A criação de instâncias de classes pode ser manipulada por uma fábrica ou por uma função de membro estático da interface. Dessa forma, até mesmo o nome da classe real, sem falar nas funções privadas, não será exposto.

7
Alexey Feldgendler

Apenas observando que a razão realmente é que o tamanho do enum ainda não é conhecido após a declaração forward. Bem, você usa a declaração avançada de uma estrutura para poder passar um ponteiro ao redor ou referir-se a um objeto de um lugar que é referenciado na própria definição de estrutura declarada adiante.

Encaminhar declarando um enum não seria muito útil, porque alguém desejaria ser capaz de passar em torno do enum por valor. Você não poderia sequer ter um ponteiro para isso, porque eu recentemente tenho dito algumas plataformas usam ponteiros de tamanho diferente para char do que para int ou long. Então tudo depende do conteúdo do enum.

O atual padrão C++ explicitamente impede fazer algo como

enum X;

(em 7.1.5.3/1). Mas o próximo C++ Standard, devido ao próximo ano, permite o seguinte, o que me convenceu realmente do problema tem a ver com o tipo subjacente:

enum X : int;

É conhecido como uma declaração enum "opaca". Você pode até usar X por valor no código a seguir. E seus enumeradores podem depois ser definidos em uma redeclaração posterior da enumeração. Veja 7.2 no rascunho de trabalho atual.

5
Johannes Schaub - litb

Eu faria assim:

[no cabeçalho público]

typedef unsigned long E;

void Foo(E e);

[no cabeçalho interno]

enum Econtent { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X,
  FORCE_32BIT = 0xFFFFFFFF };

Ao adicionar FORCE_32BIT, asseguramos que o Econtent seja compilado por um longo período, de modo que seja intercambiável com E.

4
Laurie Cheers

Parece que não pode ser declarado no GCC!

Discussão interessante aqui

2
prakash

Você pode encapsular o enum em uma struct, adicionando alguns construtores e conversões de tipo e encaminhar declarar a estrutura em vez disso.

#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
    enum e { VALUES }; \
    explicit NAME(TYPE v) : val(v) {} \
    NAME(e v) : val(v) {} \
    operator e() const { return e(val); } \
    private:\
        TYPE val; \
}

Isto parece funcionar: http://ideone.com/TYtP2

2
Leszek Swirski

Se você realmente não quer que seu enum apareça no seu arquivo de cabeçalho E certifique-se de que ele seja usado apenas por métodos privados, então uma solução pode ser seguir o princípio pimpl.

É uma técnica que garante a ocultação dos internos da classe nos cabeçalhos declarando apenas:

class A 
{
public:
    ...
private:
    void* pImpl;
};

Em seguida, no seu arquivo de implementação (cpp), você declara uma classe que será a representação dos internos.

class AImpl
{
public:
    AImpl(A* pThis): m_pThis(pThis) {}

    ... all private methods here ...
private:
    A* m_pThis;
};

Você deve criar dinamicamente a implementação no construtor da classe e excluí-la no destruidor e ao implementar o método público, você deve usar:

((AImpl*)pImpl)->PrivateMethod();

Existem profissionais para usar o pimpl, um é que ele dissocia o cabeçalho da classe de sua implementação, sem necessidade de recompilar outras classes ao alterar uma implementação de classe. Outra é que acelera seu tempo de compilação porque seus cabeçalhos são tão simples.

Mas é uma dor para usar, então você deve se perguntar se declarar seu enum como privado no cabeçalho é um problema.

2
Vincent Robert

Em meus projetos, adotei a técnica Enumeração com Limite de Namespace para lidar com enums de componentes herdados e de terceiros. Aqui está um exemplo:

forward.h:

namespace type
{
    class legacy_type;
    typedef const legacy_type& type;
}

enum.h:

// May be defined here or pulled in via #include.
namespace legacy
{
    enum evil { x , y, z };
}


namespace type
{
    using legacy::evil;

    class legacy_type
    {
    public:
        legacy_type(evil e)
            : e_(e)
        {}

        operator evil() const
        {
            return e_;
        }

    private:
        evil e_;
    };
}

foo.h:

#include "forward.h"

class foo
{
public:
    void f(type::type t);
};

foo.cc:

#include "foo.h"

#include <iostream>
#include "enum.h"

void foo::f(type::type t)
{
    switch (t)
    {
        case legacy::x:
            std::cout << "x" << std::endl;
            break;
        case legacy::y:
            std::cout << "y" << std::endl;
            break;
        case legacy::z:
            std::cout << "z" << std::endl;
            break;
        default:
            std::cout << "default" << std::endl;
    }
}

main.cc:

#include "foo.h"
#include "enum.h"

int main()
{
    foo fu;
    fu.f(legacy::x);

    return 0;
}

Observe que o cabeçalho foo.h não precisa saber nada sobre legacy::evil. Somente os arquivos que usam o tipo herdado legacy::evil (aqui: main.cc) precisam incluir enum.h.

1
mavam

Para o VC, aqui está o teste sobre declaração de encaminhamento e especificação do tipo subjacente:

  1. o seguinte código é compilado ok.
 typedef int myint; 
 enum T; 
 anula foo (T * tp) 
 {
 * tp = (T) 0x12345678; 
} 
 enum T: char 
 {
 A 
}; 

Mas recebi o aviso para/W4 (/ W3 não incorra neste aviso)

aviso C4480: extensão não padrão usada: especificando o tipo subjacente para o enum 'T'

  1. VC (Microsoft (R) 32-bit C/C++ Otimizando Compiler versão 15.00.30729.01 para 80x86) parece com erros no caso acima:

    • ao ver o enum T; VC supõe que o tipo de enumeração T usa int de 4 bytes padrão como tipo subjacente, portanto, o código de montagem gerado é:
? foo @@ YAXPAW4T @@@ Z PROC; foo 
; Arquivo e:\work\c_cpp\cpp_snippet.cpp 
; Linha 13 
 Pressione ebp 
 Mov ebp, esp 
; Linha 14 
 Mov eax, DWORD PTR _tp $ [ebp] 
 Mov DWORD PTR [eax], 305419896; 12345678H 
; Linha 15 
 Pop ebp 
 Ret 0 
? Foo @@ YAXPAW4T @@@ Z ENDP; foo 

O código de montagem acima é extraído de /Fatest.asm diretamente, não é meu palpite pessoal. Você vê o mov DWORD PTR [eax], 305419896; Linha 12345678H?

o trecho de código a seguir prova isso:

 int main (int argc, char * argv) 
 {
 união {
 char ca [4]; 
 T t; 
} a; 
 a.ca [0] = a.ca [1] = a [ca [2] = a.ca [3] = 1; 
 foo (& a. t); 
 printf ("% # x,% # x,% # x,% # x\n", a.ca [0], a.ca [1], a.ca [2] , a.ca [3]); 
 retornar 0; 
} 

o resultado é: 0x78, 0x56, 0x34, 0x12

  • depois de remover a declaração para frente do enum T e mover a definição da função foo após a definição do enum T: o resultado é OK:

a instrução chave acima se torna:

mov BYTE PTR [eax], 120; 00000078H

o resultado final é: 0x78, 0x1, 0x1, 0x1

Observe que o valor não está sendo sobrescrito

Portanto, o uso da declaração antecipada do enum em VC é considerado prejudicial.

BTW, para não surpreender, a sintaxe para declaração do tipo subjacente é o mesmo que em C #. Na prática, descobri que vale a pena salvar 3 bytes especificando o tipo subjacente como char quando falo com o sistema integrado, que é limitado à memória.

1
zhaorufei

Há alguma discordância desde que isso foi resolvido (tipo de), então aqui estão alguns bits relevantes do padrão. A pesquisa mostra que o padrão não define realmente a declaração de encaminhamento nem declara explicitamente que os enums podem ou não ser declarados para a frente.

Primeiro, de dcl.enum, seção 7.2:

O tipo subjacente de uma enumeração é um tipo integral que pode representar todos os valores enumeradores definidos na enumeração. É implementação definida que tipo integral é usado como o tipo subjacente para uma enumeração, exceto que o tipo subjacente não deve ser maior que int, a menos que o valor de um enumerador não possa caber em um int ou unsigned int. Se a lista de enumeradores estiver vazia, o tipo subjacente é como se a enumeração tivesse um único enumerador com valor 0. O valor de sizeof () aplicado a um tipo de enumeração, um objeto do tipo de enumeração ou um enumerador é o valor de sizeof () aplicado ao tipo subjacente.

Portanto, o tipo subjacente de um enum é definido pela implementação, com uma restrição menor.

Em seguida, vamos para a seção "tipos incompletos" (3.9), que é o mais próximo que chegamos de qualquer padrão em declarações futuras:

Uma classe que foi declarada mas não definida, ou uma matriz de tamanho desconhecido ou de tipo de elemento incompleto, é um tipo de objeto definido de forma incompleta.

Um tipo de classe (como "classe X") pode estar incompleto em um ponto em uma unidade de tradução e ser concluído mais tarde; o tipo "classe X" é do mesmo tipo em ambos os pontos. O tipo declarado de um objeto de matriz pode ser uma matriz de tipo de classe incompleta e, portanto, incompleta; se o tipo de classe for concluído mais tarde na unidade de tradução, o tipo de matriz será concluído; o tipo de matriz nesses dois pontos é o mesmo tipo. O tipo declarado de um objeto de matriz pode ser uma matriz de tamanho desconhecido e, portanto, estar incompleto em um ponto em uma unidade de tradução e concluído mais tarde; os tipos de matriz nesses dois pontos ("matriz de limite desconhecido de T" e "matriz de N T") são tipos diferentes. O tipo de um ponteiro para matriz de tamanho desconhecido, ou de um tipo definido por uma declaração typedef para ser uma matriz de tamanho desconhecido, não pode ser concluído.

Então, lá, o padrão praticamente definiu os tipos que podem ser declarados para a frente. O Enum não estava lá, então os autores do compilador geralmente consideram o forward declarando como não permitido pelo padrão devido ao tamanho variável de seu tipo subjacente.

Faz sentido também. Enums geralmente são referenciados em situações de valor, e o compilador precisaria saber o tamanho do armazenamento nessas situações. Como o tamanho do armazenamento é definido pela implementação, muitos compiladores podem optar por usar valores de 32 bits para o tipo subjacente de cada enum, e nesse ponto torna-se possível encaminhá-los. Um experimento interessante pode ser o de tentar declarar um enum no visual studio, forçando-o a usar um tipo subjacente maior que sizeof (int) como explicado acima para ver o que acontece.

1
Dan Olson

Minha solução para o seu problema seria:

1 - use int ao invés de enums: Declare seus ints em um namespace anônimo em seu arquivo CPP (não no cabeçalho):

namespace
{
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
}

Como seus métodos são privados, ninguém vai mexer com os dados. Você poderia até mesmo ir mais longe para testar se alguém lhe envia dados inválidos:

namespace
{
   const int FUNCTIONALITY_begin = 0 ;
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
   const int FUNCTIONALITY_end = 3 ;

   bool isFunctionalityCorrect(int i)
   {
      return (i >= FUNCTIONALITY_begin) && (i < FUNCTIONALITY_end) ;
   }
}

2: crie uma classe completa com instanciações const limitadas, como feito em Java. Encaminhar declare a classe e, em seguida, defina-a no arquivo CPP e instanciate apenas os valores semelhantes a enum. Eu fiz algo parecido em C++, e o resultado não foi tão satisfatório quanto desejado, já que precisava de algum código para simular um enum (construção de cópia, operador = etc.).

3: Como proposto anteriormente, use o enum declarado de forma privada. Apesar do fato de um usuário ver sua definição completa, ele não poderá usá-lo nem usar os métodos privados. Assim, você geralmente poderá modificar o enum e o conteúdo dos métodos existentes sem precisar recompilar o código usando sua classe.

Meu palpite seria a solução 3 ou 1.

0
paercebal