Переадресація оголошення typedef в C ++


235

Чому компілятор не дозволить мені оголосити typedef?

Якщо припустити, що це неможливо, яка найкраща практика для збереження мого дерева включення?

Відповіді:


170

Ви можете зробити вперед typedef. Але робити

typedef A B;

спочатку потрібно переслати заявити A:

class A;

typedef A B;

11
Зрештою, +1, оскільки, якщо ви технічно не можете "вперед-typedef" (тобто ви не можете написати "typedef A;"), ви можете майже напевно виконати те, що ОП хоче досягти, використовуючи ваш трюк вище.
j_random_hacker

9
Але майте на увазі, що якщо typedef зміниться, ви можете також змінити всі ці переадресації, які ви можете пропустити, якщо старий і новий typedef використовують типи з тим самим інтерфейсом.
математика

50
Взагалі це не корисне рішення. Наприклад, якщо typedefімена складного типу багаторівневого шаблону з використанням прямого оголошення, цей спосіб є досить складним і складним. Не кажучи вже про те, що це може вимагати заглиблення в деталі реалізації, заховані в аргументах шаблону за замовчуванням. І кінцеве рішення - це тривалий і нечитабельний код (особливо коли типи походять з різних просторів імен), дуже схильний до зміни оригінального типу.
Адам Бадура

3
Це також показує "деталі реалізації" (навіть якщо не повністю, але все-таки ...), тоді як ідея вперед-декларації полягала в тому, щоб приховати їх.
Адам Бадура

3
@windfinder: Це: шаблон <клас T> клас А; 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. Дивіться FILE вперед.
user877329

23

Щоб "fwd оголосив typedef", вам потрібно fwd оголосити клас або структуру, і тоді ви можете набрати dedef тип оголошень. Компілятор прийнятний декілька однакових типів.

довга форма:

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

Оскільки для оголошення типу потрібно знати його розмір. Ви можете переслати оголошення вказівника на тип або набрати вказівник на тип.

Якщо ви дійсно хочете, ви можете використовувати ідіому pimpl, щоб утримати включення. Але якщо ви хочете використовувати тип, а не покажчик, компілятор повинен знати його розмір.

Редагувати: j_random_hacker додає важливу кваліфікацію до цієї відповіді, в основному, що розмір повинен знати, щоб використовувати тип, але переадресація може бути зроблена, якщо нам потрібно лише знати, чи існує тип , щоб створити покажчики або посилання на тип. Оскільки ОП не показав код, але поскаржився, що він не складеться, я припустив (напевно правильно), що ОП намагається використовувати тип, а не просто посилатися на нього.


35
Ну а передні декларації типів класів декларують ці типи, не знаючи їх розміру. Крім того, крім можливості визначати покажчики та посилання на такі неповні типи, можуть бути оголошені (але не визначені) функції, які приймають параметри та / або повертають значення таких типів.
j_random_hacker

3
Вибачте, я не думаю, що це гарне припущення. Ця відповідь знаходиться поруч. Це дуже стосується введення попередньої декларації.
Cookie

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

У мене була одна і та ж проблема, не хотілося возитися з декількома typedefs в різних файлах, тому я вирішив це з успадкуванням:

було:

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;

0

Як зауважив Білл Коціас, єдиний розумний спосіб зберегти приватні дані вашої точки конфіденційності та передати їх заяві - це спадкування. Ви можете зробити це трохи приємніше за допомогою 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;
};

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
>>
{
};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.