Пространства имен против статических классов - PullRequest
2 голосов
/ 18 октября 2011

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

// 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 не объявлено.Может ли быть так, что и объект, и класс называются 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;
}

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

Ответы [ 3 ]

3 голосов
/ 18 октября 2011

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

static library::Foo Foo;

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

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

int Foo::some_value = 0;

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

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

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

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

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

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

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

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

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

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

2 голосов
/ 18 октября 2011

Здесь происходит две вещи:

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

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

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

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

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

Если передача Foo всем вашим методам окажется неудобной, взгляните нашаблон Facade.Идея состоит в том, чтобы создать класс 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 и перейти к первому решению: обычному объекту, только с состоянием для каждого экземпляра.

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

0 голосов
/ 18 октября 2011

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

Да. Посмотрите, например, эту запись в faq c ++ по статическому порядку инициализации фиаско. 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 не объявлен. Может ли быть так, что и объект, и класс называются Foo, даже если они находятся в разных пространствах имен?

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

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

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

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

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

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

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

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

...