Прямое объявление typedef в C ++

235

Почему компилятор не позволяет мне объявить typedef?

Предполагая, что это невозможно, как лучше сохранить дерево включения небольшим?

user96825
источник

Ответы:

170

Вы можете сделать вперед typedef. Но делать

typedef A B;

Вы должны сначала отправить декларацию A:

class A;

typedef A B;
Хонг Цзян
источник
11
+1 в конце, потому что, хотя вы технически не можете «forward-typedef» (т. Е. Вы не можете написать «typedef A;»), вы почти наверняка можете выполнить то, что хочет OP, используя ваш трюк, описанный выше.
j_random_hacker
9
Но имейте в виду, что если typedef изменится, вы также можете изменить все эти предварительные объявления, которые вы можете пропустить, если старый и новый typedef используют типы с одинаковым интерфейсом.
математика
50
В общем, это не полезное решение. Например, если typedefимена сложных многоуровневых типов шаблонов с использованием прямого объявления таким образом, являются довольно сложными и трудными. Не говоря уже о том, что это может потребовать углубления в детали реализации, скрытые в аргументах шаблона по умолчанию. И конечное решение - это длинный и нечитаемый код (особенно когда типы поступают из различных пространств имен), очень подверженный изменениям в исходном типе.
Адам Бадура
3
Также это показывает «детали реализации» (даже если не полностью, но все же ...), в то время как идея, лежащая в основе предварительного объявления, заключалась в том, чтобы скрыть их.
Адам Бадура
3
@windfinder: Это делает: template <class T> class A; typedef A <C> B;
milianw
47

Для тех из вас, кто, как я, желающих объявить структуру в стиле C, которая была определена с помощью typedef, в некотором коде c ++ я нашел решение, которое выглядит следующим образом ...

// 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;
 }
Литлджон
источник
4
@LittleJohn Проблема с этим решением заключается в том, что фиктивное имя _bah не рассматривается как часть общедоступного API. Смотрите вперед delcare FILE.
user877329
23

Чтобы "fwd объявить определение типа", вам нужно объявить класс или структуру, а затем вы можете ввести определение типа. Множественные идентичные определения типов допустимы компилятором.

длинная форма:

class MyClass;
typedef MyClass myclass_t;

короткая форма:

typedef class MyClass myclass_t;
Павел П
источник
Как это отличается от самого популярного вопроса? stackoverflow.com/a/804956/931303
Хорхе Лейтао,
1
@ JorgeLeitão ты не видишь, как это отличается? Это не показывает, как сделать это в одной строке.
Павел П
17

В C ++ (но не в простом C) совершенно законно вводить тип дважды, если оба определения полностью идентичны:

// 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);
Адам Розенфилд
источник
34
Техническое обслуживание Нет. Подобные вещи рано или поздно укусят вас в кейстер.
Марк Сторер
3
@MarkStorer, по крайней мере компилятор поймает любую разницу и выдаст ошибку. Я проверил это с помощью Visual C ++.
Алан
Хорошо, но как вы определяете Aполя таким образом, поскольку Aон пуст по определению?
Патрицио Бертони
10

Потому что, чтобы объявить тип, его размер должен быть известен. Вы можете перенаправить объявление указателя на тип или typedef указателя на тип.

Если вы действительно хотите, вы можете использовать идиому pimpl, чтобы уменьшить количество включений. Но если вы хотите использовать тип, а не указатель, компилятор должен знать его размер.

Редактировать: j_random_hacker добавляет важную квалификацию к этому ответу, в основном, что размер должен быть известен, чтобы использовать тип, но предварительное объявление может быть сделано, если нам нужно только знать, что тип существует , чтобы создать указатели или ссылки на тип. Поскольку OP не показывал код, но жаловался, что он не будет компилироваться, я предположил (вероятно, правильно), что OP пытался использовать тип, а не просто ссылаться на него.

tpdi
источник
35
Хорошо, предварительные объявления типов классов объявляют эти типы без знания их размера. Кроме того, помимо возможности определения указателей и ссылок на такие неполные типы, могут быть объявлены (но не определены) функции, которые принимают параметры и / или возвращают значение таких типов.
j_random_hacker
3
Извините, я не думаю, что это хорошее предположение. Этот ответ не имеет смысла. Это очень похоже на typedef для предварительного объявления.
Печенье
6

Использование предварительных объявлений вместо полных #includes возможно только в том случае, если вы не собираетесь использовать сам тип (в области действия этого файла), а указатель или ссылку на него.

Чтобы использовать сам тип, компилятор должен знать его размер - следовательно, должно быть видно его полное объявление - следовательно, необходим полный #include.

Однако размер указателя или ссылки известен компилятору независимо от размера указателя, поэтому достаточно предварительного объявления - оно объявляет имя идентификатора типа.

Интересно, что при использовании указателя или ссылки на classили structтипов, компилятор может обрабатывать неполные типы, избавляя вас от необходимости также объявлять типы pointee:

// 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 
Ади Шавит
источник
2

У меня была та же проблема, я не хотел связываться с несколькими определениями типов в разных файлах, поэтому я решил ее с помощью наследования:

был:

class BurstBoss {

public:

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

сделал:

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

public:

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

Работал как шарм. Конечно, мне пришлось изменить любые ссылки с

BurstBoss::ParticleSystem

просто

ParticleSystem
Билл Коциас
источник
1

Я заменил typedef( usingесли быть точным) наследованием и наследованием конструктора (?).

оригинал

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

Заменены

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

Таким образом, я смог переслать объявить CallStackс:

class CallStack;
Notinlist
источник
0

Как отметил Билл Коциас, единственный разумный способ сохранить подробности typedef вашей точки и объявить их вперед - это наследование. Вы можете сделать это немного лучше с C ++ 11, хотя. Учти это:

// 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
источник
0

Как и @BillKotsias, я использовал наследование, и оно работало на меня.

Я изменил этот беспорядок (который требовал все заголовки повышения в моем объявлении * .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;

в эту декларацию (* .h)

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

и реализация (* .cpp) была

#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
>>
{
};
Марк Лаката
источник