Альтернатива пересылаемым объявлениям, когда вы не хотите # включать

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

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

Привет

/Роберт

Ответ 1

Просто используйте интеллектуальный указатель - вы можете даже использовать 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 Data Members для рекомендации Herb Sutter.

Если вы действительно хотите, чтобы конструкция по умолчанию выполнялась автоматически, попробуйте следующее:

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

Ответ 2

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

Ничего подобного.

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

Ответ 6

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

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

Ответ 7

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

// bar.h
class foo;

class bar {
    foo& foo_;

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

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

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

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

Ответ 8

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

Чтобы предотвратить необходимость в другом #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;
}

Ответ 9

Вы также можете использовать идиом 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;
}

Вы по-прежнему получаете преимущества передовых объявлений, а также скрываете свою частную реализацию. Изменения в реализации бара не обязательно потребуют компиляции всех исходных файлов, которые #include bar.h. Сама структура реализации является самодостаточной в файле .cpp, и здесь вы можете объявлять объекты своим содержанием.

У вас небольшое поражение производительности из-за самого pImpl, но в зависимости от приложения это может быть не очень.

Я использовал идиому pImpl для больших проектов, и это имеет большое значение для времени компиляции. Жаль, что язык не может справиться с действительно частной реализацией, но там у вас есть.

Ответ 10

На самом деле существует только три альтернативы для связывания двух объектов. Вы уже обнаружили два: встроить Foo в Bar или поместить Foo в кучу и поместить Foo * в Bar. Первый требует определения класса Foo перед определением класса Bar; второй просто требует, чтобы вы отправили объявление класса Foo.

Существует третий вариант, о котором я упоминаю только потому, что вы специально исключаете оба предыдущих варианта в своем вопросе. Вы можете (в своем .cpp) создать статическую std:: map. В каждом конструкторе Bar вы добавляете Foo к этой карте с ключом this. Затем каждый член панели может найти связанный Foo, просмотрев this на карте. Bar:: ~ Bar вызовет erase(this), чтобы уничтожить Foo.

Пока это значение sizeof (Bar) не изменяется, фактическое использование памяти выше, чем включение Foo * in Bar. Вы все равно можете это сделать, если бинарная совместимость является насущной проблемой.