Пространства имен и статические classы

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

// Filename: Foo.h namespace my_project { namespace library { class Foo { public: static int some_value; // members used externally and internally Foo() { // Lots of stuff goes on in here // Therefore it's not a simply member initialization // But for this example, this should suffice some_value = 10; Foo::bar(); } static void bar() { ++some_value; } // some library function // no destructor needed because we didn't allocate anything private: // restrict copy/assignment Foo(const Foo&); void operator=(const Foo&); }; int Foo::some_value = 0; // since some_value is static, we need this } // library namespace static library::Foo Foo; } // my_project namespace 

Использование Foo было бы похоже на это, например:

 #include "Foo.h" using namespace my_project; int main() { int i = Foo.some_value; Foo.bar(); int j = Foo.some_value; return 0; } 

Конечно, этот пример очень упрощен, но он имеет смысл. Этот метод имеет четыре преимущества для меня:

  1. Пользователю библиотеки не нужно беспокоиться об инициализации. Им не нужно было бы называть что-то вроде Foo::init(); внутри их main() , потому что library::Foo был инициализирован, когда был my_project::Foo . Это основное ограничение дизайна здесь. Пользователь не должен нести ответственность за инициализацию библиотеки.

  2. Я могу создавать различные частные функции внутри библиотеки, чтобы контролировать ее использование.

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

  4. Я могу использовать . вместо :: . Но это личное дело.

Теперь вопрос в том, есть ли недостатки для этого решения? Я чувствую, что делаю то, что C ++ не собирался делать, потому что IntelliSense Visual Studio продолжает волноваться и думает, что my_project::Foo не объявлен. Может быть, потому, что и объект, и class называются Foo хотя они находятся в разных пространствах имен?

Решение компилируется отлично. Я просто волнуюсь, что, как только мой проект станет больше по масштабу, я могу начать иметь неоднозначность имени. Кроме того, я трачу лишнюю память, создавая объект этой библиотеки?

Должен ли я просто придерживаться шаблона дизайна синглтона в качестве альтернативного решения? Есть ли альтернативные решения?

ОБНОВИТЬ:

Просмотрев предоставленные решения и перескакивая через Google для различных решений, я наткнулся на extern . Я должен сказать, что я немного размыт по тому, что действительно делает это ключевое слово; С тех пор, как я изучил C ++, я был нечетким. Но после настройки моего кода я изменил его на это:

 // Foo.h namespace my_project { namespace library { class Foo_lib { public: int some_value; Foo_lib() { /* initialize library */ } void bar() { /* do stuff */ } private: // restrict copy/assignment Foo_lib(const Foo_lib&); void operator=(const Foo_lib&); }; } // library namespace extern library::Foo_lib Foo; } // my_project namespace // Foo.cpp #include "Foo.h" namespace my_project { namespace library { // Foo_lib definitions } // library namespace library::Foo_lib Foo; } // my_project namespace // main.cpp #include "Foo.h" using namespace my_project; int main() { int i = Foo.some_value; Foo.bar(); int j = Foo.some_value; return 0; } 

Это, похоже, имеет тот же эффект, что и раньше. Но, как я уже сказал, так как я все еще нечеткий по сравнению с использованием extern, будет ли это также иметь те же плохие побочные эффекты?

Эта линия особенно плоха :

 static library::Foo Foo; 

Он испускает static копию Foo в каждом переводе. Не используйте его 🙂 Результат Foo::some_value будет равен числу переводов, которые Foo.h был видимым, и он не является streamобезопасным (что может помешать вашим пользователям).

Эта строка приведет к нескольким определениям при связывании:

 int Foo::some_value = 0; 

Синглтоны тоже плохие. Поиск здесь @SO приведет к множеству причин, чтобы избежать их.

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

Пользователю библиотеки не нужно беспокоиться об инициализации. Им не нужно было бы называть что-то вроде Foo :: init (); внутри их main (), потому что library :: Foo был инициализирован, когда был создан my_project :: Foo. Это основное ограничение дизайна здесь. Пользователь не должен нести ответственность за инициализацию библиотеки.

Объекты должны иметь возможность самостоятельно строить себя, не внося непоследовательный бинарный багаж.

Я могу создавать различные частные функции внутри библиотеки, чтобы контролировать ее использование.

Это не уникально для вашего подхода.

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

Затем вы можете заставить своих пользователей передавать Foo в качестве необходимого аргумента для создания типов, от которых они зависят (где Foo необходим).

Я могу использовать. вместо ::. Но это личное дело.

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

Здесь есть две вещи:

  • Что делать, если пользователь очень хотел бы распараллелить свой код?
  • Что делать, если пользователь хотел бы начать использовать вашу библиотеку во время фазы статической инициализации?

Итак, по одному.

1. Что делать, если пользователь очень хотел бы распараллелить свой код?

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

Я бы просто рекомендовал вам сделать Foo содержать обычные атрибуты вместо static , а затем пользователь должен решить, сколько экземпляров в параллель должно использоваться и, возможно, установить на одном.

Если прохождение Foo ко всем вашим методам окажется неудобным, взгляните на шаблон « Facade . Идея здесь заключалась бы в создании classа Facade который инициализируется с помощью Foo и предоставляет точки входа в вашу библиотеку.

2. Что делать, если пользователь хотел бы начать использовать вашу библиотеку во время фазы статической инициализации?

Фиаско порядка статической инициализации просто ужасно, а фиаско порядка статического разрушения (его брат) не лучше и даже сложнее отследить (потому что память там не 0-инициализирована, так что трудно понять, что происходит).

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

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

Это можно сделать легко и безопасным способом (*), используя локальные статические переменные:

 class Foo { public: static Foo& Init() { static Foo foo; return foo; } static int GetValue() { return Init()._value; } private: Foo(): _value(1) {} Foo(Foo const&) = delete; Foo& operator=(Foo const&) = delete; int _value; }; // class Foo 

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

(*) Безопасность streamов гарантируется в C ++ 11. В C ++ 03 (версия, используемая главным образом в отрасли) лучшие компиляторы также гарантируют это, проверьте документацию, если это необходимо.

Теперь вопрос в том, есть ли недостатки для этого решения?

Да. См., Например, эту запись в c ++ faq о фиаско порядка статического инициализации. http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14 tldr? По сути, вы не можете контролировать, какой порядок статических объектов (например, Foo выше) инициализируется, любые предположения о порядке (например, инициализация одного статического объекта со значениями из другого) приведут к неопределенному поведению.

Рассмотрим этот код в моем приложении.

 #include "my_project/library/Foo.h" static int whoKnowsWhatValueThisWillHave = Foo::some_value; int main() { return whoKnowsWhatValueThisWillHave; } 

Нет никаких гарантий того, что я возвращаюсь из main () здесь.

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

На самом деле, нет … Поскольку все ваши данные статичны, все новые экземпляры – это, по сути, пустые оболочки, указывающие на одни и те же данные. В принципе, у вас есть копия.

Я чувствую, что делаю то, что C ++ не собирался делать, потому что IntelliSense Visual Studio продолжает волноваться и думает, что my_project :: Foo не объявлен. Может быть, потому, что и объект, и class называются Foo, хотя они находятся в разных пространствах имен?

Вы! Предположим, я добавлю эту строку в свой код:

использование пространства имен :: my_project :: library;

что теперь разрешает «Foo»? Возможно, это определено в стандарте, но, по крайней мере, это запутывает.

Я могу использовать. вместо ::. Но это личное дело.

Не боритесь с языком. Если вы хотите кодировать синтаксис Python или Java, используйте Python или Java (или Ruby или что-то еще) …

Должен ли я просто придерживаться шаблона дизайна синглтона в качестве альтернативного решения? Есть ли альтернативные решения?

Да, Синглтон – хороший, но вы также должны рассмотреть, действительно ли вам нужен синглтон. Поскольку ваш пример является синтаксическим, трудно сказать, но, возможно, было бы лучше использовать инъекцию зависимостей или что-то похожее на минимизацию / устранение жестких связей между classами.

Надеюсь, я не повредил твои чувства 🙂 Хорошо спросить вопросы, но, очевидно, вы уже это знаете!