Как предоставить много конструкторов, но без слишком большого количества зависимостей? - PullRequest
3 голосов
/ 07 февраля 2010

Это довольно простой вопрос о дизайне 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 и возврата построенного объекта? Любые другие варианты?

Ответы [ 6 ]

10 голосов
/ 07 февраля 2010

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

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 голос
/ 07 февраля 2010

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

struct FooBuild
{
  // attributes of Foo
};

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

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

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

Я взял идею от Python, и его frozenset class:)

1 голос
/ 07 февраля 2010

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

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

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

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

1 голос
/ 07 февраля 2010

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

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

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

0 голосов
/ 07 февраля 2010

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

Первая - сложная проблема, решаемая в C ++, потому что язык требует монолитных, автономных TU. (Сравните с таким языком, как Go, где дизайнеры научились избегать этой проблемы из своего опыта работы с Си.) Шаблоны справка, как и при использовании только прямых объявлений ("объявлений, которые не являются определениями ") когда вам не нужны определения. (Хотя иронично, что шаблоны помогают, так как на практике им требуется весь их код в заголовках.) В основном длительное время сборки - это то, с чем мы просто имеем дело с в текущем 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 к другому, поэтому весь этот шаблон значительно упрощен.

0 голосов
/ 07 февраля 2010

Pattern Builder - это то, что вы ищете.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...