Классы участников и #include - PullRequest
4 голосов
/ 30 января 2011

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

Однако дляклассы, в которых переменные-члены определены как тип другого класса, какова альтернатива?

Например, я делал вещи следующим образом в течение самого длительного времени:

/* Header file for class myGrades */
#include <vector>           //bad
#include "classResult.h"    //bad

class myGrades
{
    vector<classResult> grades;
    int average;
    int bestScore;
}

(Пожалуйста, извинитетот факт, что это очень искусственный пример)

Итак, если я хочу избавиться от #include строк, есть ли способ сохранить vector или мне нужно подойти к программированию моегокод совершенно по-другому?

Ответы [ 7 ]

3 голосов
/ 30 января 2011

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

Однако существуют методы, известные как брандмауэры компилятора, которые могут позволить вам структурировать классы таким образом, чтобы дать классу доступ к объектам соответствующего типа безнепосредственно включая их.Одним из них является идиома pImpl , в которой реализация вашего класса выглядит следующим образом:

class MyClass {
public:
    /* ... */

private:
    struct Impl;
    Impl* pImpl;
};

Здесь вы заранее объявляете структуру Impl, содержащую реализацию вашего класса, а затем сохраняетеуказатель на структуру Impl в классе.Поскольку наличие указателя в вашем классе не требует, чтобы размер объекта, на который он указывал, был известен, это прекрасно.Затем в файле .cpp вы можете определить структуру Impl:

struct MyClass::Impl {
    /* ... */
};

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

Другой вариант в том же духе - сделать класс абстрактным базовым классом,затем предоставить статическую фактор-функцию, которая возвращает объект, подклассифицирующий ваш базовый класс, который фактически содержит реализацию.Например:

class MyClass {
public:
    virtual ~MyClass(); /* Need virtual dtors in polymorphic classes! */

    virtual void doSomething() = 0;
    /* ... etc. ... */

    static MyClass* New(/* ... args ... */);
};

Затем в файле .cpp вы можете определить конкретный подкласс MyClass, который фактически выполняет всю работу:

class ActualClass: public MyClass {
public:
     void doSomething();
     /* ... etc. ... */

private:
     /* ... data members ... */
}

и, наконец, реализовать New для создания нового экземпляра класса реализации:

MyClass* MyClass::New(/* ... args ... */) {
    return new ActualClass(/* ... args ... */);
}

Надеюсь, это поможет!

2 голосов
/ 30 января 2011

плохо иметь #include в заголовочных файлах

Не думаю, что вы можете сказать это с такой степенью уверенности.Если у вас есть функция в заголовочном файле, которая принимает std :: vector, вы действительно не можете не включать этот заголовочный файл.

Вы правы, что вы не должны включать НЕОБХОДИМЫЕ включения в заголовочные файлы,Если вам нужно что-то для вашей реализации, которое не нужно показывать в заголовочном файле, сохраняйте это в вашей реализации.

Но я бы не стал отказываться от включения чего-либо в заголовки.

2 голосов
/ 30 января 2011

Нет ничего плохого в том, что в заголовок включены другие заголовки.

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

1 голос
/ 30 января 2011

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

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

То, что хотят те, кто использует ваш код, совершенно не относится к делу. Если они собираются использовать ваш класс, тогда им НУЖНЫ все определения объектов, от которых напрямую зависит ваш класс (а не только зависимости имени). Если вы используете переменную-член типа WTF, то кто-то просто ДОЛЖЕН включить заголовок для WTF, чтобы использовать ваш класс, и он должен быть включен до определения вашего класса. НАМНОГО, НАМНОГО, НАМНОГО лучше включить такие заголовки самостоятельно, чтобы клиентам вашего класса не приходилось выяснять, где определен WTF fsck, и включать его, прежде чем включать заголовок вашего класса.

Другими словами, делайте именно то, что вам сказали. Это хорошая форма, чтобы включить все заголовки, необходимые для объявления вашего класса.

0 голосов
/ 04 февраля 2011

Ух ты, я совершенно не прав.Ривьера подошел к нему со своим комментарием.

Мой источник был около 7:30 - http://channel9.msdn.com/shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Standard-Template-Library-STL-3-of-n/

Однако, возвращаясь к этому, он имел в виду директивы using.Я, должно быть, был умом, слушая его.

Интересные ответы, особенно templatetypedef.Спасибо, ребята (и извинения за беспокойство)

0 голосов
/ 30 января 2011

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

Часто хорошей идеей является наличие одного заголовочного файла, который включает только минимальное минимальное количество, которое должен знать компилятор, и другого, который содержит любые определения встроенных функций. sizeof (vector) обычно равен 2 * sizeof (classResult *), он не зависит от того, что находится в classResult, поэтому в первоначальном примере вам нужно только объявить classResult.

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

Упомянутая выше идиома pimpl также может быть полезной, но требует некоторой осторожности относительно того, когда и где она используется, поскольку она может снизить производительность во время выполнения, требуя дополнительной косвенности. Если объект только когда-либо создается в куче, тогда вы можете сделать ctor частным, и вместо этого использовать публичный статический метод фабрики. Реализации функции могут привести это к реальному типу, например.

// In header file
class Foo
{
public:

    static Foo * Create();

    void DoSomething();

private:
    Foo();
    Foo( const Foo & );
    Foo & operator=( const Foo & );
};


// In c++ file
struct FooImplementation
{
    int bar;
};

Foo * Foo::Create()
{
    return (Foo*) new FooImplementation;
}

#include <stdio.h>

void Foo::DoSomething()
{
    FooImplementation * const self = (FooImplementation*) this;
    printf( "bar = %i", self->bar );
}

Основные проблемы с этим подходом заключаются в том, что sizeof (Foo) не дает правильного значения (следовательно, функция Create ()), и что это хак. Лично я чувствую, что повышение производительности за счет сокращения времени компиляции делает этот взлом полезным.

0 голосов
/ 30 января 2011

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

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

...