Declaración de reenvío de typedef en C ++

235

¿Por qué el compilador no me permite declarar un typedef?

Suponiendo que sea imposible, ¿cuál es la mejor práctica para mantener pequeño mi árbol de inclusión?

usuario96825
fuente

Respuestas:

170

Puede reenviar typedef. Pero para hacer

typedef A B;

primero debe reenviar declarar A:

class A;

typedef A B;
Hong Jiang
fuente
11
+1 al final porque si bien técnicamente no puedes "reenviar typedef" (es decir, no puedes escribir "typedef A;"), casi seguro que puedes lograr lo que el OP quiere lograr usando tu truco anterior.
j_random_hacker
99
Pero tenga en cuenta que si el typedef cambia, también puede cambiar todas esas declaraciones de reenvío, lo que puede pasar por alto si el typedef antiguo y el nuevo usan tipos con la misma interfaz.
Matemáticas
50
En general, esta no es una solución útil. Por ejemplo, si typedefnombra un tipo de plantilla complejo de varios niveles que utiliza una declaración directa de esta manera, es bastante complejo y difícil. Sin mencionar que podría requerir sumergirse en detalles de implementación ocultos en los argumentos de plantilla predeterminados. Y la solución final es un código extenso e ilegible (especialmente cuando los tipos provienen de varios espacios de nombres) muy propensos a cambiar en el tipo original.
Adam Badura
3
Además, esto muestra "detalles de implementación" (aunque no sea completamente, pero aún así ...) mientras que la idea detrás de la declaración adelantada era ocultarlos.
Adam Badura
3
@windfinder: Sí: plantilla <clase T> clase A; typedef A <C> B;
milianw
47

Para aquellos de ustedes como yo, que buscan declarar hacia adelante una estructura de estilo C que se definió usando typedef, en algún código de C ++, he encontrado una solución que sigue de la siguiente manera ...

// 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;
 }
Pequeño John
fuente
44
@LittleJohn El problema con esta solución es que el nombre ficticio _bah no se considera parte de la API pública. Ver archivo delcare adelante.
user877329
23

Para "fwd declarar un typedef" necesita fwd declarar una clase o una estructura y luego puede escribir typedef declarado tipo. Múltiples definiciones de tipo idénticas son aceptables por el compilador.

forma larga:

class MyClass;
typedef MyClass myclass_t;

forma corta:

typedef class MyClass myclass_t;
Pavel P
fuente
¿Cómo es esto diferente de la pregunta más votada? stackoverflow.com/a/804956/931303
Jorge Leitao
1
@ JorgeLeitão, ¿no ves cómo es diferente? No muestra cómo hacerlo en una línea.
Pavel P
17

En C ++ (pero no en C simple), es perfectamente legal escribir un tipo dos veces, siempre y cuando ambas definiciones sean 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
fuente
34
Mantenimiento No No. Este tipo de cosas te morderán en el keister tarde o temprano.
Mark Storer
3
@ MarkStorer, al menos el compilador detectará cualquier diferencia y generará un error. Verifiqué esto con Visual C ++.
Alan
Bien, pero ¿cómo define los Acampos de esta manera ya que Aestá vacío por definición?
Patrizio Bertoni
10

Porque para declarar un tipo, su tamaño necesita ser conocido. Puede reenviar declarar un puntero al tipo, o typedef un puntero al tipo.

Si realmente quieres, puedes usar el modismo de pimpl para mantener bajas las inclusiones. Pero si desea utilizar un tipo, en lugar de un puntero, el compilador debe conocer su tamaño.

Editar: j_random_hacker agrega una calificación importante a esta respuesta, básicamente que se necesita saber el tamaño para usar el tipo, pero se puede hacer una declaración hacia adelante si solo necesitamos saber que el tipo existe , para crear punteros o referencias al tipo. Como el OP no mostró código, pero se quejó de que no se compilaría, supuse (probablemente correctamente) que el OP estaba tratando de usar el tipo, no solo referirme a él.

tpdi
fuente
35
Bueno, las declaraciones directas de tipos de clase declaran estos tipos sin conocer su tamaño. Además, además de poder definir punteros y referencias a tales tipos incompletos, se pueden declarar (pero no definir) funciones que toman parámetros y / o devuelven un valor de tales tipos.
j_random_hacker
3
Lo siento, no creo que sea una buena suposición. Esta respuesta no viene al caso. Este es en gran medida el caso de typedef una declaración de reenvío.
Cookie
6

El uso de declaraciones adelantadas en lugar de un total de #includes es posible sólo cuando se está sin la intención de utilizar el tipo en sí (en el ámbito de este archivo), pero un puntero o referencia a ella.

Para usar el tipo en sí, el compilador debe conocer su tamaño, por lo tanto, debe verse su declaración completa, por lo tanto, #includese necesita un completo .

Sin embargo, el compilador conoce el tamaño de un puntero o referencia, independientemente del tamaño del puntero, por lo que una declaración directa es suficiente: declara un nombre de identificador de tipo.

Curiosamente, cuando se usa puntero o referencia a classo structtipos, el compilador puede manejar tipos incompletos ahorrándole la necesidad de declarar también los tipos de puntas:

// 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
fuente
2

Tuve el mismo problema, no quería meterme con múltiples typedefs en diferentes archivos, así que lo resolví con la herencia:

estaba:

class BurstBoss {

public:

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

hizo:

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

public:

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

Trabajado como un encanto. Por supuesto, tuve que cambiar cualquier referencia de

BurstBoss::ParticleSystem

simplemente

ParticleSystem
Bill Kotsias
fuente
1

Reemplacé el typedef( usingpara ser específico) con herencia y herencia de constructor (?).

Original

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

Sustituido

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;
};

De esta manera pude reenviar la declaración CallStackcon:

class CallStack;
No en lista
fuente
0

Como señaló Bill Kotsias, la única forma razonable de mantener privados los detalles de typedef de su punto, y declararlos de antemano es mediante herencia. Sin embargo, puedes hacerlo un poco mejor con C ++ 11. Considera esto:

// 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
fuente
0

Al igual que @BillKotsias, utilicé la herencia, y funcionó para mí.

Cambié este desastre (que requería todos los encabezados de impulso en mi declaración * .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;

en esta declaración (* .h)

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

y la implementación (* .cpp) fue

#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
fuente