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

StackOverflow https://stackoverflow.com/questions/305849

Вопрос

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

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

С уважением

/Роберт

Это было полезно?

Решение

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

Другие советы

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

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

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

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

То, что вы хотите, не может быть сделано на 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;
};

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

С этим ничего не поделаешь.

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

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

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

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

// bar.h
class foo;

class bar {
    foo& foo_;

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

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

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

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

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

Чтобы предотвратить необходимость в другом #include, вы можете включить это myAuto класс в заголовке prefix для вашего проекта, или вы можете скопировать и вставить его в каждый заголовок (не очень хорошая идея, но это сработало бы).

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

Вы также могли бы использовать идиому 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 не обязательно потребуют компиляции всех исходных файлов, которые #включают bar.h.Сама структура реализации содержится в файле .cpp, и здесь вы можете объявлять объекты по своему усмотрению.

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top