Как использовать внутренние typedef обоих классов при взаимном включении заголовочных файлов?
Введение:
Допустим, есть два класса A и B, определённые каждый в своей паре файлов .h и .cpp. Код файлов представлен ниже:
Файл A.h
#pragma once
class A
{
public:
A(void);
~A(void);
void f(B b);
};
Файл A.cpp:
#include "B.h"
#include "A.h"
A::A(void)
{
}
A::~A(void)
{
}
void A::f(B b)
{
}
Файл B.h:
#pragma once
class B
{
public:
B(void);
~B(void);
void f(A a);
};
Файл B.cpp
#include "B.h"
#include "A.h"
B::B(void)
{
}
B::~B(void)
{
}
void B::f(A a)
{
}
Если попытаться собрать такой проект, например, с таким файлом main.cpp:
#include "B.h"
#include "A.h"
int main()
{
return 0;
}
получится ошибка, т.к. при компиляции файла B.cpp обнаружится, что в B.h тип A не определён, т.к. в файле B.cpp B.h был подключён раньше, чем A.h. Если поменять директивы включения местами, получим похожую ошибку, только уже при компиляции файла A.cpp.
Проблема давно известная и решается, насколько я знаю, следующим способом: порядок включения заголовочных файлов в каждом cpp-файле делается одинаковым (как уже сделано в коде выше), а в B.h добавляется строчка class A;, говорящая, что класс A есть, но про него компилятору расскажут попозже. Таким образом, описанная проблема решена. Но это только введение.
Вопрос:
Допустим теперь, что в каждом из классов есть внутренний typedef, более коротко и понятно определяющий умный указатель на данный класс. Более того, метод f в каждом из классов теперь принимает не объект другого класса, а тот самый умный указатель на него. Код классов видоизменяется следующим образом:
Файл A.h
#pragma once
#include <memory>
class A
{
public:
typedef std::tr1::shared_ptr<A> Ptr;
A(void);
~A(void);
void f(B::Ptr b);
};
Файл A.cpp:
#include "B.h"
#include "A.h"
A::A(void)
{
}
A::~A(void)
{
}
void A::f(B::Ptr b)
{
}
Файл B.h:
#pragma once
#include <memory>
class A;
class B
{
public:
typedef std::tr1::shared_ptr<B> Ptr;
B(void);
~B(void);
void f(A::Ptr a);
};
Файл B.cpp
#include "B.h"
#include "A.h"
B::B(void)
{
}
B::~B(void)
{
}
void B::f(A::Ptr a)
{
}
(использую Visual Studio 2008, поэтому такое пространство имён для shared_ptr).
И вот теперь начинаются действительно проблемы: при сборке, а именно при компиляции файла A.cpp, в файле B.h появляется ошибка
error C2027: use of undefined type 'A'
что логично, поскольку я просто указал компилятору, что класс A существует, но не дал никаких гарантий, что внутри него есть соответствующий typedef.
Решение этой задачи костылём тоже известно: можно просто в файле B.h использовать не typedef, а полное определение типа, однако всё же хочется пользоваться удобствами укорочения имени типов, которые любезно предоставляет мне механизм typedef. Отсюда:
Вопрос: как использовать typedef из обоих классов, если они объявлены во взаимно включённых заголовочных файлах?
Ответы (2 шт):
Предварительная декларации типов обычно заводят в отдельном хедере. А внутренние типы класса можно декларировать в личном пространстве имён класса. Отдельно от декларации самого класса. При компиляции сначала обрабатываются предварительно объявленные типы, а затем если надо то и сам класс.
Atype.h
class A ;
namespace nsA {
typedef std/*::tr1*/::shared_ptr<A> Ptr;
}
Btype.h
class B ;
namespace nsB {
typedef std/*::tr1*/::shared_ptr<B> Ptr;
}
A.h
#pragma once
#include <memory>
# include "Btype.h"
class A
{
public:
A(void);
~A(void);
void f(nsB::Ptr b);
};
B.h
#pragma once
#include <memory>
# include "Atype.h"
class B
{
public:
B(void);
~B(void);
void f(nsA::Ptr a);
};
A.cpp
#include "B.h"
#include "A.h"
A::A(void)
{
}
A::~A(void)
{
}
void A::f(nsB::Ptr b)
{
}
B.cpp
#include "B.h"
#include "A.h"
B::B(void)
{
}
B::~B(void)
{
}
void B::f(nsA::Ptr a)
{
}
Я сгласен с уже данным ответом, но как альтернативу могу предложить traits:
template<class>
struct traits;
template<>
struct traits<A>
{
typedef ... Ptr;
};
template<>
struct traits<B>
{
typedef ... Ptr;
};
Тоже таки вынести из класса, но ассоциация теснее. И в классе можно подключить свои traits через наследование для того, чтобы иметь и вложенный синтаксис
struct A : public traits<A>