2010-02-06 3 views
3

Это довольно простой C++ дизайн вопрос:Как обеспечить множество конструкторов, но не слишком много зависимостей?

У меня есть класс, который содержит некоторые данные, которые считываются только после того, как объект построен:

class Foo { 
private: 
    class Impl; 
    Impl* impl_; 
public: 
    int get(int i); // access internal data elements 
}; 

Теперь я хотел бы реализовать несколько способов построить объект Foo и заполнить его данными: от std::istream, от итератора, вектора и т. д. Каков наилучший способ реализовать это?

Я мог бы просто добавить все эти конструктор непосредственно в Foo, но я не хочу, чтобы Foo пользователю придется включать std::istream и т.д. Я тоже беспокоюсь о классах, содержащих слишком много коды.

Какой самый идиоматический способ сделать это? Я полагаю, добавьте некоторую приватную функцию addElement, а затем определите функции фабрики друзей, которые создают объекты Foo, читая данные, вызывая addElement и вернув построенный объект? Любые другие варианты?

ответ

10

Если вы хотите, чтобы построить что-то из ряда, возможно:

class X 
{ 
public: 
    template <class InputIterator> 
    X(InputIterator first, InputIterator last); 
}; 

Использование:

//from array 
X a(array, array + array_size); 

//from vector 
X b(vec.begin(), vec.end()); 

//from stream 
X c((std::istream_iterator<Y>(std::cin)), std::istream_iterator<Y>()); 
1

Я мог бы просто добавить все эти конструкторов непосредственно в Foo, но я не сделать действительно хочу, чтобы пользователь Foo, чтобы включить зЬй :: IStream и т.д. Я также беспокоюсь о классах, содержащих слишком много кода; пользователь может захотеть создать много объектов Foo, но я не хочу, чтобы каждый объект содержал много код строительства.

Ваш конструктор для std :: istream может переслать объявление и передать его в качестве ссылки. Таким образом, ваш пользователь не должен включать istream, чтобы включить Foo.h, но вам нужно включить istream в свой Foo.cpp.

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

+2

Я думаю, что '#include ' может быть, что один ищет (для объявляющих потоков вперед). – UncleBens

1

Я мог бы просто добавить все эти конструкторов непосредственно в Foo, но я не сделать действительно хочу, чтобы пользователь Foo, чтобы включить зЬй :: IStream т.д.

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

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

Вы, похоже, смущены. Каждый объект не содержит копии кода - существует только один экземпляр.

+0

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

+0

Это не по эстетическим соображениям, это ограничение количества вещей, которые необходимо изменить при изменении внутреннего представления класса. То есть, чтобы увеличить инкапсуляцию. У Скотта Мейера есть статья об этом плавании. –

+0

@ Dan: Это не дает вам инкапсуляцию. Если члену нужен только доступ к публичному интерфейсу, это все, что вам нужно использовать при его реализации; вам не нужно напрямую обращаться к непубличным вещам только потому, что они там. То, что это дает вам, - это проверяется компилятором, а не беглым взглядом (четко обозначать непубличные и легко заглядывать), и это не имеет для меня почти достаточного значения, чтобы он влиял на член и не являлся членом , (Есть важные причины, чтобы сделать вещи не членами, а просто то, что вы сказали, часто цитируется, но не один из них.) – 2010-02-06 21:55:59

0

Вы говорите о двух разных типах кода: 1) размер исходного кода (который влияет только на время сборки) и 2) размер исполняемого файла («скомпилированный код» или сегмент кода). Они коррелированы, но определенно не то же самое.

Первый трудный труд решить в C++, потому что язык требует монолитных автономных TU. (Сравните с таким языком, как Go, где разработчики научились избегать этой проблемы из своего опыта работы с C.) Templates help, как и только с помощью forward declarations («объявления, которые не являются определениями»), когда вам не нужны определения. (Хотя иронично, что шаблоны помогают, поскольку они требуют всего их кода в заголовках, на практике.) В основном длинные времена сборки - это то, что мы только deal with в текущем C++.

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

struct SpanBase { 
    SpanBase(int start, int stop, int step) 
    : start(start), stop(stop), step(step) 
    // any complex code in the init list would normally be duplicated 
    // in each Span ctor 
    { 
    IMAGINE("complex code executed by each Span ctor"); 
    if (start > stop) throw std::logic_error("Span: start exceeds stop"); 
    } 
protected: 
    int start, stop, step; 
}; 

struct Span : protected SpanBase { 
    // Protected inheritance lets any class derived from Span access members 
    // which would be protected in Span if SpanBase didn't exist. If Span 
    // does not have any, then private inheritance can be used. 

    Span(int stop) : SpanBase(0, stop, 1) {} 
    Span(int start, int stop) : SpanBase(start, stop, 1) {} 

    Span(int start, int stop, int step): StepBase(start, stop, step) {} 
    // this one could be handled by a default value, but that's not always true 
}; 

И, наконец, C++ 0x позволяет передавать от одного CTOR к другому, так что вся эта картина значительно упрощена.

1

Существует одно простое решение: использование другого класса в качестве промежуточного.

struct FooBuild 
{ 
    // attributes of Foo 
}; 

class Foo 
{ 
public: 
    Foo(const FooBuild&); 

private: 
    // attributes of Foo, some of them const 
}; 

Тогда любой человек может легко настроить FooBuild как один пожелания, и построить Foo объект из этого. Таким образом, вам не нужно предоставлять слишком много конструкторов, и вы все равно можете поддерживать класс инвариантов для Foo с первой проверкой, как обычно, в конструкторе.

Я взял идею из питона, а его frozenset класса :)

Смежные вопросы