Declaração de encaminhamento de um typedef em C ++

235

Por que o compilador não me permite encaminhar declarar um typedef?

Supondo que seja impossível, qual é a melhor prática para manter minha árvore de inclusão pequena?

user96825
fonte

Respostas:

170

Você pode avançar typedef. Mas fazer

typedef A B;

você deve primeiro encaminhar declarar A:

class A;

typedef A B;
Hong Jiang
fonte
11
+1 no final porque, tecnicamente, não é possível "forward-typedef" (ou seja, não é possível escrever "typedef A;"), é quase certo que você pode realizar o que o OP deseja realizar usando seu truque acima.
Jrandom_hacker 30/04/09
9
Mas esteja ciente de que, se o typedef mudar, você também poderá alterar todas essas declarações avançadas, o que poderá perder se o typedef antigo e o novo usarem tipos com a mesma interface.
math
50
Em geral, essa não é uma solução útil. Por exemplo, se os typedefnomes de um modelo de modelo multinível complexo usando uma declaração de encaminhamento dessa maneira são bastante complexos e difíceis. Sem mencionar que pode ser necessário mergulhar nos detalhes da implementação ocultos nos argumentos do modelo padrão. E a solução final é um código longo e ilegível (especialmente quando os tipos vêm de vários espaços para nome) muito propensos a alterações no tipo original.
Adam Badura
3
Além disso, isso mostra "detalhes de implementação" (mesmo que não totalmente, mas ainda ...) enquanto a idéia por trás da declaração de encaminhamento era escondê-los.
Adam Badura
3
@ windfinder: Faz: template <classe T> classe A; typedef A <C> B;
milianw
47

Para aqueles que gostam de mim, que desejam encaminhar declarar uma estrutura no estilo C que foi definida usando typedef, em algum código c ++, encontrei uma solução que é a seguinte ...

// a.h
 typedef struct _bah {
    int a;
    int b;
 } bah;

// b.h
 struct _bah;
 typedef _bah bah;

 class foo {
   foo(bah * b);
   foo(bah b);
   bah * mBah;
 };

// b.cpp
 #include "b.h"
 #include "a.h"

 foo::foo(bah * b) {
   mBah = b;
 }

 foo::foo(bah b) {
   mBah = &b;
 }
Pequeno John
fonte
4
@LittleJohn O problema com esta solução é que o nome fictício _bah não é considerado parte da API pública. Consulte o arquivo de encaminhamento direto.
user877329
23

Para "declarar um typedef", você precisa declarar uma classe ou uma estrutura e, em seguida, pode digitar o tipo declarado. Vários typedefs idênticos são aceitáveis ​​pelo compilador.

forma longa:

class MyClass;
typedef MyClass myclass_t;

forma curta:

typedef class MyClass myclass_t;
Pavel P
fonte
Como isso é diferente da pergunta mais votada? stackoverflow.com/a/804956/931303
Jorge Leitao
1
@ JorgeLeitão você não vê como é diferente? Não mostra como fazer isso em uma linha.
Pavel P
17

No C ++ (mas não no C), é perfeitamente legal digitar um tipo duas vezes, desde que ambas as definições sejam completamente idênticas:

// foo.h
struct A{};
typedef A *PA;

// bar.h
struct A;  // forward declare A
typedef A *PA;
void func(PA x);

// baz.cc
#include "bar.h"
#include "foo.h"
// We've now included the definition for PA twice, but it's ok since they're the same
...
A x;
func(&x);
Adam Rosenfield
fonte
34
Manutenção Não. Não. Esse tipo de coisa vai morder você mais cedo ou mais tarde.
Mark Storer
3
@ MarkStorer, pelo menos o compilador perceberá qualquer diferença e gerará um erro. Eu verifiquei isso com o Visual C ++.
Alan
Bom, mas como você define os Acampos dessa maneira, uma vez que Aé vazio por definição?
Patrizio Bertoni
10

Como para declarar um tipo, seu tamanho precisa ser conhecido. Você pode encaminhar declarar um ponteiro para o tipo ou digitar um ponteiro para o tipo.

Se você realmente deseja, pode usar o idioma pimpl para manter as inclusões baixas. Mas se você deseja usar um tipo, e não um ponteiro, o compilador precisa saber seu tamanho.

Edit: j_random_hacker adiciona uma qualificação importante a esta resposta, basicamente que o tamanho precisa ser conhecido para usar o tipo, mas uma declaração direta pode ser feita se precisarmos apenas saber que o tipo existe , a fim de criar ponteiros ou referências para o tipo tipo. Como o OP não mostrou código, mas reclamou que não seria compilado, presumi (provavelmente corretamente) que o OP estava tentando usar o tipo, não apenas se referindo a ele.

tpdi
fonte
35
Bem, declarações avançadas de tipos de classe declaram esses tipos sem o conhecimento de seu tamanho. Além disso, além de poder definir ponteiros e referências a esses tipos incompletos, as funções podem ser declaradas (mas não definidas), que aceitam parâmetros e / ou retornam um valor para esses tipos.
j_random_hacker
3
Desculpe, não acho que seja uma boa suposição. Esta resposta não vem ao caso. Este é muito o caso de typedef uma declaração direta.
22411 Cookie
6

O uso de declarações avançadas em vez de um #includes completo é possível apenas quando você não pretende usar o próprio tipo (no escopo deste arquivo), mas um ponteiro ou referência a ele.

Para usar o próprio tipo, o compilador deve conhecer seu tamanho - portanto, sua declaração completa deve ser vista -, portanto, #includeé necessário um total .

No entanto, o tamanho de um ponteiro ou referência é conhecido pelo compilador, independentemente do tamanho do apontador, portanto, uma declaração de encaminhamento é suficiente - ele declara um nome de identificador de tipo.

Curiosamente, ao usar ponteiro ou referência a classou structtipos, o compilador pode lidar com tipos incompletos, economizando a necessidade de encaminhar também os tipos de ponta de ponte:

// header.h

// Look Ma! No forward declarations!
typedef class A* APtr; // class A is an incomplete type - no fwd. decl. anywhere
typedef class A& ARef;

typedef struct B* BPtr; // struct B is an incomplete type - no fwd. decl. anywhere
typedef struct B& BRef;

// Using the name without the class/struct specifier requires fwd. decl. the type itself.    
class C;         // fwd. decl. type
typedef C* CPtr; // no class/struct specifier 
typedef C& CRef; // no class/struct specifier 

struct D;        // fwd. decl. type
typedef D* DPtr; // no class/struct specifier 
typedef D& DRef; // no class/struct specifier 
Adi Shavit
fonte
2

Eu tinha o mesmo problema, não queria mexer com vários typedefs em arquivos diferentes, então resolvi com herança:

foi:

class BurstBoss {

public:

    typedef std::pair<Ogre::ParticleSystem*, bool> ParticleSystem; // removed this with...

fez:

class ParticleSystem : public std::pair<Ogre::ParticleSystem*, bool>
{

public:

    ParticleSystem(Ogre::ParticleSystem* system, bool enabled) : std::pair<Ogre::ParticleSystem*, bool>(system, enabled) {
    };
};

Funcionou como um encanto. Claro, eu tive que mudar as referências de

BurstBoss::ParticleSystem

simplesmente

ParticleSystem
Bill Kotsias
fonte
1

Substituí o typedef( usingpara ser específico) por herança e herança de construtor (?).

Original

using CallStack = std::array<StackFrame, MAX_CALLSTACK_DEPTH>;

Substituído

struct CallStack // Not a typedef to allow forward declaration.
  : public std::array<StackFrame, MAX_CALLSTACK_DEPTH>
{
  typedef std::array<StackFrame, MAX_CALLSTACK_DEPTH> Base;
  using Base::Base;
};

Dessa forma, eu pude encaminhar declarar CallStackcom:

class CallStack;
Não está na lista
fonte
0

Como Bill Kotsias observou, a única maneira razoável de manter os detalhes digitados do seu ponto em particular, e depois declará-los é com herança. Você pode fazê-lo um pouco melhor com o C ++ 11. Considere isto:

// LibraryPublicHeader.h

class Implementation;

class Library
{
...
private:
    Implementation* impl;
};
// LibraryPrivateImplementation.cpp

// This annoyingly does not work:
//
//     typedef std::shared_ptr<Foo> Implementation;

// However this does, and is almost as good.
class Implementation : public std::shared_ptr<Foo>
{
public:
    // C++11 allows us to easily copy all the constructors.
    using shared_ptr::shared_ptr;
};
Timmmm
fonte
0

Como @BillKotsias, eu usei herança, e funcionou para mim.

Eu mudei essa bagunça (que exigia todos os cabeçalhos de impulso na minha declaração * .h)

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/moment.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/max.hpp>

typedef boost::accumulators::accumulator_set<float,
 boost::accumulators::features<
  boost::accumulators::tag::median,
  boost::accumulators::tag::mean,
  boost::accumulators::tag::min,
  boost::accumulators::tag::max
 >> VanillaAccumulator_t ;
std::unique_ptr<VanillaAccumulator_t> acc;

nesta declaração (* .h)

class VanillaAccumulator;
std::unique_ptr<VanillaAccumulator> acc;

e a implementação (* .cpp) foi

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/moment.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/max.hpp>

class VanillaAccumulator : public
  boost::accumulators::accumulator_set<float,
    boost::accumulators::features<
      boost::accumulators::tag::median,
      boost::accumulators::tag::mean,
      boost::accumulators::tag::min,
      boost::accumulators::tag::max
>>
{
};
Mark Lakata
fonte