Я обычно, почти не задумываясь, использую форвардные объявления, чтобы мне не приходилось включать заголовки. Что-то вроде этого примера:
//-----------------------
// foo.h
//-----------------------
class foo
{
foo();
~foo();
};
//-----------------------
// bar.h
//-----------------------
class foo; // forward declaration
class bar
{
bar();
~bar();
foo* foo_pointer;
};
Некоторым разработчикам нравится использовать этот метод, чтобы избежать проблем с кругами включения. Я скорее использую его, чтобы минимизировать накладные расходы в обширных иерархиях включения, важной части физического проектирования (в частности, для более крупных проектов).
Однако в некоторых случаях мне очень нравится объявлять члены как обычные объекты вместо указателей, чтобы воспользоваться автоматическим механизмом построения / уничтожения. Это приводит к проблеме, заключающейся в том, что форвардные объявления больше не могут использоваться, поскольку в таком случае компилятору требуется определение класса, например:
//-----------------------
// foo.h
//-----------------------
class foo
{
foo();
~foo();
};
//-----------------------
// bar.h
//-----------------------
class foo; // Not enough given the way we declare "foo_object"..
#include "foo.h" // ..instead this is required
class bar
{
bar();
~bar();
foo foo_object;
};
Итак, я был бы счастлив, если бы кто-нибудь знал альтернативную языковую конструкцию, которую можно было бы использовать здесь, чтобы я мог объявить «foo_object», как показано в примере, но без включения его заголовка.
С уважением
/Роберт





Если вы можете использовать ссылку, вы можете сохранить тот же синтаксис использования. Однако ваша ссылка должна быть инициализирована прямо в конструкторе, поэтому ваш ctor обязательно должен быть определен вне линии. (Вам также нужно будет освободить объект в деструкторе.)
// bar.h
class foo;
class bar {
foo& foo_;
public:
bar();
~bar();
};
// bar.cc
bar::bar() : foo_(*new foo)
{
// ...
}
bar::~bar()
{
// ...
delete &foo_;
}
Ваш пробег может отличаться. :-)
Вы не можете. Компилятору необходимо знать размер объекта при объявлении класса.
Ссылки являются альтернативой, хотя они должны быть созданы во время создания, поэтому это не всегда возможно.
Другой альтернативой являются умные указатели, но я полагаю, что технически это все еще указатель.
Было бы хорошо знать, почему вы не хотите использовать указатель, чтобы предложить какую-то другую конструкцию ...
Член может быть инициализирован по умолчанию и должным образом (полностью) установлен позже, например, сеттеры. Вы не можете сделать это для референтного члена.
Я хочу использовать механизм автоматического построения / разрушения, поэтому любой альтернативы, подразумевающей требуемые задачи в конструкторе / деструкторе класса хоста, к сожалению, для меня недостаточно. Спасибо.
То, что вы хотите, невозможно сделать на C++. Чтобы сгенерировать код для объекта, вашему компилятору необходимо знать, сколько памяти требуется его классу. Чтобы знать это, он должен знать, сколько памяти требуется для каждого члена класса.
Если вы хотите создать класс типа bar с членом типа foo, компилятор должен знать, насколько велик foo. Единственный способ узнать это, если у него есть определение foo (через #include). В противном случае ваш единственный вариант - использовать прямое объявление foo и указатель или ссылку вместо фактического объекта foo.
Нет никакого способа обойти это.
Лучше всего ограничить, сколько включается, но вы должны включить файл с объявлением класса. Вы можете разделить объявление класса на отдельный заголовок, который, надеюсь, не включает ничего другого. Тогда да, у вас должен быть #include, но вы по-прежнему сохраняете свою иерархию include несколько поверхностной. В конце концов, включение одного файла обходится дешево, только когда иерархия простирается до сотен или тысяч файлов, это начинает мешать ...;)
Практически единственное, что вы можете сделать, - это минимизировать влияние используя идиому pImpl, так что, когда вы включаете foo.h, вы включаете только интерфейс foo.
Вы не можете избежать включения foo.h, но вы можете сделать его как можно дешевле. Привычка использовать foward-объявления вместо #inlcudes помогает вам на этом пути.
Просто используйте умный указатель - в этом случае вы даже можете использовать auto_ptr.
//-----------------------
// bar.h
//-----------------------
#include <memory>
class foo; // Not enough given the way we declare "foo_object"..
class bar
{
public:
bar();
~bar();
foo &foo_object() { return *foo_ptr; }
const foo &foo_object() const { return *foo_ptr; }
private:
auto_ptr<foo> foo_ptr;
};
Вы получаете все преимущества автоматического управления памятью, не зная ничего о foo в bar.h. См. Обертывание элементов данных указателя для рекомендаций Херба Саттера.
Если вы действительно хотите, чтобы построение по умолчанию происходило автоматически, попробуйте следующее:
#include <iostream>
using namespace std;
class Foo;
template <typename T>
class DefaultConstuctorPtr
{
T *ptr;
void operator =(const DefaultConstuctorPtr &);
DefaultConstuctorPtr(const DefaultConstuctorPtr &);
public:
DefaultConstuctorPtr() : ptr(new T()) {}
~DefaultConstuctorPtr() { delete ptr; }
T *operator *() { return ptr; }
const T *operator *() const { return ptr; }
};
class Bar
{
DefaultConstuctorPtr<Foo> foo_ptr;
public:
Bar() {} // The compiler should really need Foo() to be defined here?
};
class Foo
{
public:
Foo () { cout << "Constructing foo"; }
};
int main()
{
Bar bar;
}
Разве для этого еще не требуется инициализация foo_ptr в конструкторе бара?
Я слышал, что автоматические указатели обесцениваются, кто-нибудь еще может получить лучший ответ?
ролл-сам, как некоторые из нас описали ниже. Даже если стандарт C++ удалит auto_ptr, эти самодельные классы все равно будут работать.
Даже если они не рекомендуют auto_ptr (чего я не слышал о том, чтобы комитет по стандартам делал), C++ x0 все равно будет иметь shared_ptr и weak_ptr.
Интересно, что это действительно работает из-за использования шаблонов (то есть, если вы сделаете DefaultConstructorPtr обычным классом с Foo, заменяющим T везде, компилятор будет жаловаться на то, что Foo неизвестен). Я немного поэкспериментировал с этим, и кажется, что вы даже можете переместить определение Foo ниже main (), и оно все еще работает. Кроме того, если вы затем сделаете ctor по умолчанию для Foo закрытым, компилятор конкретно пожалуется, что Foo :: Foo () является закрытым. С другой стороны, если вы добавите другой метод и попытаетесь вызвать его из main (), компилятор пожалуется, что Foo не определено. Продолж ...
Но если вы теперь переместите определение Foo прямо над main () - снова нет проблем. Кажется, что все в шаблоне компилируется после всего остального. Интересно, продиктовано ли такое поведение стандартом или зависит от реализации.
Шаблоны компилируются только наполовину, пока не будут использованы.
Вы можете использовать собственный класс «умного указателя», который автоматически создает и уничтожает экземпляр. Это приведет к автоматическому строительству и разрушению, к которому вы стремитесь.
Чтобы предотвратить необходимость в другом #include, вы можете включить этот класс myAuto в заголовок префикса своего проекта или скопировать и вставить его в каждый заголовок (не очень хорошая идея, но это сработает).
template<class T>
class myAuto
{
private:
T * obj;
public:
myAuto() : obj(new T) { }
~myAuto() { delete obj; }
T& object() { return *obj; }
T* operator ->() { return obj; }
};Вот как вы бы это использовали:
// foo.h:
class foo
{
public:
foo();
~foo();
void some_foo_func();
};//bar.h:
class foo;
class bar
{
public:
bar();
~bar();
myAuto<foo> foo_object;
};
//main.cc:
#include "foo.h"
#include "bar.h"
int main()
{
bar a_bar;
a_bar.foo_object->some_foo_func();
return 0;
}Как утверждали другие, вы не можете этого сделать по причинам, о которых они тоже заявили :) Затем вы сказали, что не хотите заботиться о создании / разрушении членов в классе, содержащем их. Для этого можно использовать шаблоны.
template<typename Type>
struct member {
boost::shared_ptr<Type> ptr;
member(): ptr(new Type) { }
};
struct foo;
struct bar {
bar();
~bar();
// automatic management for m
member<foo> m;
};
Я думаю, что код не требует пояснений. Если возникнут какие-то вопросы, баги меня, пожалуйста
Вы также можете использовать идиому pImpl, например:
//-----------------------
// foo.h
//-----------------------
class foo
{
foo();
~foo();
};
//-----------------------
// bar.h
//-----------------------
class foo;
class bar
{
private:
struct impl;
boost::shared_ptr<impl> impl_;
public:
bar();
const foo& get_foo() const;
};
//-----------------------
// bar.cpp
//-----------------------
#include "bar.h"
#include "foo.h"
struct bar::impl
{
foo foo_object;
...
}
bar::bar() :
impl_(new impl)
{
}
const foo& bar::get_foo() const
{
return impl_->foo_object;
}
Вы по-прежнему получаете преимущества форвардных объявлений, плюс вы скрываете свою частную реализацию. Изменения в реализации bar не обязательно потребуют компиляции всех исходных файлов, содержащих #include bar.h. Сама структура реализации является автономной в файле .cpp, и здесь вы можете объявлять объекты по своему усмотрению.
У вас небольшое снижение производительности из-за самого pImpl, но в зависимости от приложения это может не иметь большого значения.
Я использовал идиому pImpl для больших проектов, и время компиляции очень важно. Жаль, что язык не может справиться с по-настоящему частной реализацией, но вот она.
На самом деле есть только три альтернативы для связывания двух объектов. Вы уже обнаружили два: встроить Foo в Bar или поместить Foo в кучу и поместить Foo * в Bar. Первый требует определения класса Foo перед определением класса Bar; второй просто требует, чтобы вы переадресовали объявление класса Foo.
Существует третий вариант, о котором я упоминаю только потому, что вы специально исключаете оба предыдущих варианта в своем вопросе. Вы можете (в своем .cpp) создать статический std :: map. В каждом конструкторе Bar вы добавляете к этой карте Foo с ключом this. Затем каждый участник бара может найти соответствующий Foo, просмотрев this на карте. Bar :: ~ Bar вызовет erase(this), чтобы уничтожить Foo.
Хотя при этом sizeof (Bar) остается неизменным, фактическое использование памяти выше, чем при включении Foo * в Bar. Тем не менее, вы все равно можете сделать это, если двоичная совместимость является насущной проблемой.
Технически объект-член не так уж отличается от ссылки в том, что касается создания экземпляра при построении, поэтому здесь нет потерь. :-)