Déclaration directe d'un typedef en C ++

235

Pourquoi le compilateur ne me laisse-t-il pas déclarer un typedef?

En supposant que c'est impossible, quelle est la meilleure pratique pour garder mon arbre d'inclusion petit?

user96825
la source

Réponses:

170

Vous pouvez faire un typedef avant. Mais pour faire

typedef A B;

vous devez d'abord déclarer A:

class A;

typedef A B;
Hong Jiang
la source
11
+1 à la fin parce que, bien que vous ne puissiez techniquement pas "faire un typage avant" (c'est-à-dire que vous ne pouvez pas écrire "typedef A;"), vous pouvez presque certainement accomplir ce que l'OP veut accomplir en utilisant votre astuce ci-dessus.
j_random_hacker
9
Mais sachez que si le typedef change, vous pouvez également modifier toutes ces déclarations avancées, ce qui peut vous manquer si l'ancien et le nouveau typedef utilisent des types avec la même interface.
math
50
En général, ce n'est pas une solution utile. Par exemple, si le typedefnom d'un type de modèle multiniveau complexe utilisant une déclaration directe de cette façon est plutôt complexe et difficile. Sans oublier que cela pourrait nécessiter de plonger dans les détails d'implémentation cachés dans les arguments de modèle par défaut. Et la solution finale est un code long et illisible (en particulier lorsque les types proviennent de divers espaces de noms) très susceptible de changer de type d'origine.
Adam Badura
3
Cela montre également les "détails de l'implémentation" (même si ce n'est pas complètement mais quand même ...) alors que l'idée derrière la déclaration avant était de les cacher.
Adam Badura
3
@windfinder: Il le fait: modèle <classe T> classe A; typedef A <C> B;
milianw
47

Pour ceux d'entre vous comme moi, qui cherchent à déclarer une structure de style C qui a été définie à l'aide de typedef, dans du code c ++, j'ai trouvé une solution qui va comme suit ...

// 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;
 }
LittleJohn
la source
4
@LittleJohn Le problème avec cette solution est que le nom fictif _bah n'est pas considéré comme faisant partie de l'API publique. Voir le fichier FILE delcare.
user877329
23

Pour "fwd declare a typedef", vous devez déclarer fwd une classe ou une structure, puis vous pouvez typedef déclaré le type. Plusieurs typedefs identiques sont acceptables par le compilateur.

forme longue:

class MyClass;
typedef MyClass myclass_t;

forme courte:

typedef class MyClass myclass_t;
Pavel P
la source
En quoi est-ce différent de la question la plus votée? stackoverflow.com/a/804956/931303
Jorge Leitao
1
@ JorgeLeitão vous ne voyez pas en quoi c'est différent? Il ne montre pas comment faire cela en une seule ligne.
Pavel P
17

En C ++ (mais pas en C simple), il est parfaitement légal de taper un type deux fois, tant que les deux définitions sont complètement identiques:

// 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
la source
34
Entretien Non Non. Ce genre de chose vous mordra tôt ou tard dans le keister.
Mark Storer
3
@MarkStorer, au moins le compilateur va détecter toute différence et générer une erreur. J'ai vérifié cela avec Visual C ++.
Alan
Bien, mais comment définissez-vous les Achamps de cette manière car Aest vide par définition?
Patrizio Bertoni
10

Parce que pour déclarer un type, sa taille doit être connue. Vous pouvez transmettre déclarer un pointeur sur le type, ou typedef un pointeur sur le type.

Si vous le voulez vraiment, vous pouvez utiliser l'idiome pimpl pour réduire les inclusions. Mais si vous souhaitez utiliser un type plutôt qu'un pointeur, le compilateur doit connaître sa taille.

Edit: j_random_hacker ajoute une qualification importante à cette réponse, fondamentalement que la taille doit être connue pour utiliser le type, mais une déclaration directe peut être faite si nous avons seulement besoin de savoir que le type existe , afin de créer des pointeurs ou des références à la type. Étant donné que l'OP n'affichait pas de code, mais se plaignait qu'il ne se compilerait pas, j'ai supposé (probablement correctement) que l'OP essayait d' utiliser le type, pas seulement de s'y référer.

tpdi
la source
35
Eh bien, les déclarations directes des types de classe déclarent ces types sans connaître leur taille. De plus, en plus de pouvoir définir des pointeurs et des références à de tels types incomplets, des fonctions peuvent être déclarées (mais non définies) qui prennent des paramètres et / ou renvoient une valeur de ces types.
j_random_hacker
3
Désolé, je ne pense pas que ce soit une bonne hypothèse. Cette réponse est hors de propos. C'est très bien le cas de typedef a forward declaration.
Cookie
6

L'utilisation de déclarations avancées au lieu d'un #includes complet n'est possible que lorsque vous n'avez pas l' intention d'utiliser le type lui-même (dans la portée de ce fichier) mais un pointeur ou une référence à celui-ci.

Pour utiliser le type lui-même, le compilateur doit connaître sa taille - d'où sa déclaration complète doit être vu - d'où un plein #includeest nécessaire.

Cependant, la taille d'un pointeur ou d'une référence est connue du compilateur, quelle que soit la taille de la pointe, donc une déclaration directe est suffisante - elle déclare un nom d'identificateur de type.

Fait intéressant, lorsque vous utilisez un pointeur ou une référence à des types classou struct, le compilateur peut gérer les types incomplets, vous évitant ainsi de déclarer les types de pointe également:

// 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
la source
2

J'ai eu le même problème, je ne voulais pas jouer avec plusieurs typedefs dans différents fichiers, donc je l'ai résolu avec l'héritage:

était:

class BurstBoss {

public:

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

fait:

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

public:

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

A fonctionné comme un charme. Bien sûr, j'ai dû changer toutes les références de

BurstBoss::ParticleSystem

simplement

ParticleSystem
Bill Kotsias
la source
1

J'ai remplacé le typedef( usingpour être précis) par l'héritage et l'héritage du constructeur (?).

Original

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

Remplacé

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 cette façon, j'ai pu transmettre déclarer CallStackavec:

class CallStack;
Pas dans la liste
la source
0

Comme l'a noté Bill Kotsias, la seule façon raisonnable de garder les détails typedef de votre point privé et de les déclarer en avant est avec héritage. Vous pouvez cependant le faire un peu mieux avec C ++ 11. Considère ceci:

// 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
la source
0

Comme @BillKotsias, j'ai utilisé l'héritage et cela a fonctionné pour moi.

J'ai changé ce gâchis (qui nécessitait tous les en-têtes boost dans ma déclaration * .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;

dans cette déclaration (* .h)

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

et l'implémentation (* .cpp) a été

#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
la source