Альтернатива пересылке объявлений, когда вы не хотите #include - PullRequest
10 голосов
/ 20 ноября 2008

Я обычно, почти не задумываясь, использую предварительные объявления, чтобы мне не пришлось включать заголовки. Что-то в этом примере:

//-----------------------
// 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", как показано в примере, но без включения его заголовка.

Привет

/ Robert

Ответы [ 10 ]

12 голосов
/ 20 ноября 2008

Вы не можете. Компилятор должен знать размер объекта при объявлении класса.

Ссылки являются альтернативой, хотя их необходимо создавать во время строительства, поэтому это не всегда возможно.

Другой альтернативой являются умные указатели, но я полагаю, что технически это все еще указатель.

Было бы хорошо узнать, почему вы не хотите использовать указатель, чтобы предложить какую-то другую конструкцию ...

7 голосов
/ 20 ноября 2008

Просто используйте умный указатель - в этом случае вы даже можете использовать 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;
}
7 голосов
/ 20 ноября 2008

То, что вы хотите, не может быть сделано в C ++. Чтобы сгенерировать код для объекта, ваш компилятор должен знать, сколько памяти требует его класс. Чтобы знать это, он должен знать, сколько памяти требуется для каждого члена класса.

Если вы хотите создать класс типа bar с членом типа foo, компилятор должен знать, насколько велик foo. Единственный способ узнать это, если у него есть определение foo (через #include). В противном случае единственный вариант - использовать прямое объявление foo и указатель или ссылку вместо реального объекта foo.

2 голосов
/ 20 ноября 2008

Как говорили другие, вы не можете сделать это по причинам, которые они указали :) Затем вы сказали, что не хотите заботиться о создании / уничтожении членов класса, в котором они содержатся. Вы можете использовать шаблоны для этого.

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;
};

Я думаю, что код не требует пояснений. Если возникнут какие-либо вопросы, напишите мне, пожалуйста.

1 голос
/ 20 ноября 2008

Практически единственное, что вы можете сделать, - это минимизировать влияние на , используя идиому pImpl , чтобы при включении foo.h вы включали только интерфейс foo.

Вы не можете не включать foo.h, но вы можете сделать его как можно дешевле. Привычка, которую вы выработали, используя объявления foward, а не #inlcudes, помогает вам в этом.

1 голос
/ 20 ноября 2008

Обойти это невозможно.

Лучше всего ограничить количество включаемого, но вы должны включить файл с объявлением класса. Вы можете разделить объявление класса в отдельный заголовок, который, надеюсь, не содержит ничего другого. Тогда да, у вас должен быть #include, но вы все еще сохраняете иерархию включений несколько поверхностной. В конце концов, в том числе один файл дешев, только когда иерархия простирается на сотни или тысячи файлов, он начинает болеть ...;)

0 голосов
/ 21 ноября 2008

На самом деле есть только три альтернативы, чтобы связать два объекта. Вы уже обнаружили два: вставьте 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. Вы все еще можете сделать это, если бинарная совместимость является насущной проблемой.

0 голосов
/ 20 ноября 2008

Вы также можете использовать идиому 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 для больших проектов, и это существенно меняет время компиляции. Жаль, что язык не может обработать действительно частную реализацию, но у вас есть это.

0 голосов
/ 20 ноября 2008

Вы можете использовать пользовательский класс «интеллектуальный указатель», который автоматически создает и уничтожает экземпляр. Это обеспечит автоматическое строительство и разрушение, за которым вы будете.

Чтобы избежать необходимости в другом #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;
}
0 голосов
/ 20 ноября 2008

Если вы можете использовать ссылку, вы можете сохранить тот же синтаксис использования. Однако ваша ссылка должна быть сразу инициализирована в конструкторе, поэтому ваш ctor обязательно должен быть определен вне строки. (Вам также необходимо освободить объект в деструкторе.)

// bar.h
class foo;

class bar {
    foo& foo_;

public:
    bar();
    ~bar();
};

// bar.cc
bar::bar() : foo_(*new foo)
{
    // ...
}

bar::~bar()
{
    // ...
    delete &foo_;
}

Ваш пробег может отличаться. : -)

...