Переадресація декларування вкладених типів / класів у C ++


197

Нещодавно я застряг у такій ситуації:

class A
{
public:
    typedef struct/class {...} B;
...
    C::D *someField;
}

class C
{
public:
    typedef struct/class {...} D;
...
    A::B *someField;
}

Зазвичай ви можете оголосити ім'я класу:

class A;

Але ви не можете переслати оголошення про вкладений тип, наступні причини викликають помилку компіляції.

class C::D;

Будь-які ідеї?


6
Навіщо це вам потрібно? Зауважте, що ви можете переслати оголошення, якщо це визначений член того ж класу: клас X {клас Y; Y * a; }; клас X :: Y {};
Йоханнес Шауб - ліб

Це рішення працює для мене (простору імен C {класу D;};): stackoverflow.com/questions/22389784 / ...
Альберт Wiersch

Я знайшов посилання
bitlixi

Відповіді:


224

Ви не можете цього зробити, це отвір у мові C ++. Вам доведеться розв’язати хоча б один із вкладених класів.


6
Дякую за відповідь. У моєму випадку це не мої вкладені класи. Я сподівався уникнути величезної залежності файлових заголовків бібліотеки з невеликим прямим посиланням. Цікаво, чи виправив це C ++ 11?
Марш Рей

61
Ой. Тільки те, що я не хотів, щоб Google з’являвся. Все одно дякую за стислу відповідь.
Learnvst

19
Тут же ... хтось знає, чому це неможливо? Здається, є справжні випадки використання, і ця відсутність перешкоджає послідовності архітектури в деяких ситуаціях.
Maël Nison

Ви можете використовувати товариша. І просто додайте коментар до того, що ви використовуєте його, щоб обійти дірку в C ++.
Ерік Аронесті

3
Щоразу, коли я стикаюся з такими непотрібними вадами цієї ерзацької мови, я розриваюсь між сміхом і плачем
SongWithoutWords

33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

Мені потрібна була пряма посилання на кшталт:

class IDontControl::Nested; // But this doesn't work.

Моє вирішення було:

class IDontControl_Nested; // Forward reference to distinct name.

Пізніше, коли я міг використовувати повне визначення:

#include <idontcontrol.h>

// I defined the forward ref like this:
class IDontControl_Nested : public IDontControl::Nested
{
    // Needed to make a forwarding constructor here
    IDontControl_Nested(int i) : Nested(i) { }
};

Ця методика, мабуть, матиме більше проблем, ніж варто, якби були складні конструктори чи інші спеціальні функції, які не успадковувалися гладко. Я міг уявити, що певна магія шаблону погано реагує.

Але в моєму дуже простому випадку це, здається, працює.


16
У C ++ 11 ви можете успадкувати конструктори за using basename::basename;похідним класом, таким чином, не виникає проблем зі складними циторами.
Xeo

1
Хороший трюк, але він не спрацює, якщо вказівник на IDontControl :: Вкладений, що використовується в одному заголовку (де він оголошений вперед) і звертається за допомогою зовнішнього коду, який також включає повне визначення IDontControl. (Тому що компілятор не буде відповідати IDontControl_Nested та IDontControl :: Вкладений). Обхід полягає у виконанні статичної ролі.
Артем Писаренко

Я б рекомендував робити навпаки і мати клас поза, а просто використовувати typedefвсередині класу
райдерхоф

3

Якщо ви дійсно хочете уникати #including неприємного файлу заголовка у вашому файлі заголовка, ви можете зробити це:

hpp-файл:

class MyClass
{
public:
    template<typename ThrowAway>
    void doesStuff();
};

cpp-файл

#include "MyClass.hpp"
#include "Annoying-3rd-party.hpp"

template<> void MyClass::doesStuff<This::Is::An::Embedded::Type>()
{
    // ...
}

Але з іншого боку:

  1. вам доведеться вказати вбудований тип під час виклику (особливо якщо ваша функція не приймає жодних параметрів вбудованого типу)
  2. ваша функція не може бути віртуальною (тому що це шаблон)

Отже, так, компроміси ...


1
Що за чорт hppфайл?
Naftali aka Neal

7
lol, файл заголовка .hpp використовується в проектах C ++, щоб відрізнити його від файлу заголовка C, який зазвичай закінчується .h. Працюючи з C ++ та C в одному проекті, деякі люди віддають перевагу .hpp та .cpp для файлів C ++, щоб це чітко визначити, з яким типом файлів вони мають справу, та .h та .c для файлів C.
бітек

2

Це можна зробити, оголосивши зовнішній клас як простір імен .

Зразок: ми повинні використовувати вкладений клас others :: A :: Вкладений в others_a.h, який поза нашим контролем.

інші_а.х

namespace others {
struct A {
    struct Nested {
        Nested(int i) :i(i) {}
        int i{};
        void print() const { std::cout << i << std::endl; }
    };
};
}

my_class.h

#ifndef MY_CLASS_CPP
// A is actually a class
namespace others { namespace A { class Nested; } }
#endif

class MyClass {
public:
    MyClass(int i);
    ~MyClass();
    void print() const;
private:
    std::unique_ptr<others::A::Nested> _aNested;
};

my_class.cpp

#include "others_a.h"
#define MY_CLASS_CPP // Must before include my_class.h
#include "my_class.h"

MyClass::MyClass(int i) :
    _aNested(std::make_unique<others::A::Nested>(i)) {}
MyClass::~MyClass() {}
void MyClass::print() const {
    _aNested->print();
}

1
Це може працювати, але це недокументовано. Причина, чому це працює, полягає в тому, що a::bвони керуються однаково, незалежно від того, чи aє клас чи простір імен.
jaskmar

3
Не працює з Clang або GCC. У ньому сказано, що зовнішній клас був оголошений як щось інше, ніж простір імен.
Дуги

1

Я б не назвав це відповіддю, але, тим не менш, цікавою знахідкою: Якщо ви повторите декларацію своєї структури в просторі імен під назвою C, все добре (принаймні в gcc). Коли знайдено визначення класу C, здається, він мовчки перезаписав нампростір C.

namespace C {
    typedef struct {} D;
}

class A
{
public:
 typedef struct/class {...} B;
...
C::D *someField;
}

class C
{
public:
   typedef struct/class {...} D;
...
   A::B *someField;
}

1
Я спробував це з cygwin gcc, і він не компілюється, якщо ви спробуєте посилатися на A.someField. C: D у визначенні класу A насправді посилається на (порожню) структуру в просторі імен, а не на структуру в класі C (BTW це не компілюється в MSVC)
Dolphin

Це дає помилку: "клас C" переосмислений як символ іншого виду "
Кальмарій

9
Схоже на помилку GCC. Здається, що ім'я простору імен може приховати ім’я класу в тій же області.
Йоханнес Шауб - ліб

0

Це було б вирішенням рішення (принаймні, для проблеми, описаної у питанні, - не для фактичної проблеми, тобто, коли не має контролю над визначенням C):

class C_base {
public:
    class D { }; // definition of C::D
    // can also just be forward declared, if it needs members of A or A::B
};
class A {
public:
    class B { };
    C_base::D *someField; // need to call it C_base::D here
};
class C : public C_base { // inherits C_base::D
public:
    // Danger: Do not redeclare class D here!!
    // Depending on your compiler flags, you may not even get a warning
    // class D { };
    A::B *someField;
};

int main() {
    A a;
    C::D * test = a.someField; // here it can be called C::D
}

0

Якщо у вас є доступ до зміни вихідного коду класів C і D, ви можете вийняти клас D окремо і ввести його синонім у клас C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.