Альтернатива пересылке объявлений, когда вы не хотите #include
Я обычно, почти не задумываясь, использую предварительные объявления, чтобы мне не пришлось включать заголовки. Что-то в этом примере:
//-----------------------
// 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", как показано в примере, но без включения его заголовка.
С уважением
/Роберт
10 ответов
Просто используйте умный указатель - вы даже можете использовать 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. Смотрите рекомендацию Херба Саттера об элементах данных Wrapping Pointer.
Если вы действительно хотите, чтобы построение по умолчанию происходило автоматически, попробуйте это:
#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;
}
Ты не можешь Компилятор должен знать размер объекта при объявлении класса.
Ссылки являются альтернативой, хотя их нужно создавать во время строительства, поэтому это не всегда возможно.
Другой альтернативой являются умные указатели, но я полагаю, что технически это все еще указатель.
Было бы хорошо узнать, почему вы не хотите использовать указатель, чтобы предложить какую-то другую конструкцию, хотя...
То, что вы хотите, не может быть сделано в C++. Чтобы сгенерировать код для объекта, ваш компилятор должен знать, сколько памяти требует его класс. Чтобы знать это, он должен знать, сколько памяти требуется для каждого члена класса.
Если вы хотите создать класс типа bar с членом типа foo, компилятор должен знать, насколько велик foo. Единственный способ узнать это, если у него есть определение foo (через #include). В противном случае, единственный вариант - использовать прямое объявление foo и указатель или ссылку вместо реального объекта foo.
Как утверждали другие, вы не можете делать это по причинам, которые они указали:) Затем вы сказали, что не хотите заботиться о создании / уничтожении членов класса, в котором они содержатся. Вы можете использовать шаблоны для этого.
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;
};
Я думаю, что код не требует пояснений. Если возникнут какие-либо вопросы, напишите мне, пожалуйста.
Обойти это невозможно.
Лучше всего ограничить количество включаемого, но вы должны включить файл с объявлением класса. Вы можете разделить объявление класса на отдельный заголовок, который, надеюсь, не содержит ничего другого. Тогда да, у вас должен быть #include, но вы все еще сохраняете иерархию включений несколько поверхностной. В конце концов, в том числе один файл дешев, только когда иерархия простирается до сотен или тысяч файлов, он начинает болеть...;)
Практически единственное, что вы можете сделать, это минимизировать влияние, используя идиому pImpl, чтобы при включении foo.h вы включали только интерфейс foo.
Вы не можете не включать foo.h, но можете сделать его как можно дешевле. Привычка, которую вы выработали, используя объявления foward, а не #inlcudes, поможет вам в этом.
На самом деле есть только три альтернативы, чтобы связать два объекта. Вы уже обнаружили два: вставьте Foo в Bar или поместите Foo в кучу и поместите Foo* в Bar. Первый требует определения класса Foo перед определением класса Bar; второй просто требует, чтобы вы объявили класс Foo.
Существует третий вариант, который я упоминаю только потому, что вы специально исключили оба предыдущих варианта в своем вопросе. Вы можете (в вашем.cpp) создать статическую карту std::map. В каждом конструкторе Bar вы добавляете Foo к этой карте, this
, Каждый член бара может затем найти связанный Foo, посмотрев вверх this
на карте. Бар::~ Бар позвонит erase(this)
уничтожить фу.
Несмотря на то, что это сохраняет sizeof (Bar) без изменений, фактическое использование памяти выше, чем включение Foo* в Bar. Вы все еще можете сделать это, если бинарная совместимость является насущной проблемой.
Вы также можете использовать идиому 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 для больших проектов, и это существенно меняет время компиляции. Жаль, что язык не может обработать действительно частную реализацию, но там у вас есть.
Если вы можете использовать ссылку, вы можете сохранить тот же синтаксис использования. Однако ваша ссылка должна быть сразу инициализирована в конструкторе, поэтому ваш ctor обязательно должен быть определен вне строки. (Вам также необходимо освободить объект в деструкторе.)
// bar.h
class foo;
class bar {
foo& foo_;
public:
bar();
~bar();
};
// bar.cc
bar::bar() : foo_(*new foo)
{
// ...
}
bar::~bar()
{
// ...
delete &foo_;
}
Ваш пробег может отличаться.:-)
Вы можете использовать пользовательский класс "умный указатель", который автоматически создает и уничтожает экземпляр. Это обеспечит автоматическое строительство и разрушение, за которым вы будете.
Чтобы предотвратить необходимость в другом #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;
}