Статическая, нечленная или статическая нечленная функция?

Каждый раз, когда у меня есть какая-то функциональность, относящаяся к «полезности», я в конечном итоге задаюсь вопросом, какой вариант лучше. Например, печать структур сообщений (собственных или внешних), некоторый код кодирования/декодирования или просто несколько полезных функций преобразования в контексте, в котором я работаю.

Варианты, о которых я думаю, следующие:

1) Статическая функция во вспомогательном классе/структуре.

struct helper
{
    static bool doSomething(...);
};

2) Функция, не являющаяся членом.

namespace helper
{
    bool doSomething(...);
}

3) Статическая функция, не являющаяся членом.

namespace helper
{
    static bool doSomething(...);
}

В некоторых случаях может потребоваться инициализировать или сохранить состояние в «утилите», поэтому я выбираю вариант 1, чтобы избежать «глобального» состояния. Однако, если нет состояния, которое нужно сохранить, следует ли мне выбрать вариант 2 или 3? В чем практическая разница между вариантом 2 и 3?

Что важно учитывать и есть ли предпочтительный способ для этого? Спасибо!


person murrekatt    schedule 05.08.2011    source источник
comment
Вариант (3) просто делает функцию локальной для единицы перевода, что может быть не очень полезно, поскольку вы можете использовать только функцию внутри TU, которая ее определяет. См. также этот вопрос.   -  person Kerrek SB    schedule 05.08.2011


Ответы (3)


Разница между вариантами 2 и 3 в том, что во втором случае функция будет внутренней по отношению к единице перевода. Если функция определена только в cpp, это должен быть вариант (который примерно эквивалентен безымянному пространству имен — это четвертый вариант для рассмотрения, опять же примерно эквивалентный 3).

Если функция должна использоваться разными единицами перевода, вам следует выбрать вариант 2. Функция будет скомпилирована один раз (если вы не пометите ее как inline и не предоставите определение в заголовке), а с вариантом 3 компилятор создаст ее. внутренняя копия в каждой единице перевода.

Что касается варианта 1, я бы его избегал. В Java или C# вы вынуждены использовать классы везде, и в итоге вы получаете служебные классы, когда операции не соответствуют объектной парадигме. С другой стороны, в C++ вы можете предоставить эти операции как автономные функции, и нет необходимости добавлять дополнительный уровень. Если вы выберете служебный класс, не забудьте отключить создание объектов.

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

double do_maths( double x ) {
   using namespace math;
   return my_sqrt( x ) * cube_root(x);
}
// alternatively with an utility class:
double do_maths( double x ) {
   return math::my_sqrt(x) * math::cube_root(x);
}

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

person David Rodríguez - dribeas    schedule 05.08.2011
comment
+1 за это ›› но я предпочитаю первое: внутри функции я могу выбрать пространство имен и затем просто сосредоточиться на операциях и игнорировать проблемы поиска. ‹‹ Важный момент ! - person Nawaz; 05.08.2011

Не используйте классы в качестве пространств имен. Использование свободной (т.е. не являющейся членом) функции внутри пространства имен — это самая простая вещь.

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

Использование классов в качестве пространства имен глупо: зачем создавать класс, экземпляр которого вы не создадите?

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

Увы, люди с опытом работы на C#/Java привыкли делать эту глупость, но это потому, что разработчики их языков в одностороннем порядке решили, что бесплатные функции — это зло. Расстреливать их или нет - вопрос религии.

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

person Alexandre C.    schedule 05.08.2011
comment
Конечно. Это все еще альтернатива, особенно если нужно сохранить состояние. - person murrekatt; 05.08.2011
comment
+1 за это ›› Whether they should be shot is a matter of religion. :D - person Nawaz; 05.08.2011
comment
Читая ваш ответ, кажется, что я нажал кнопку. - person murrekatt; 05.08.2011
comment
@murrekatt: статическая переменная-член так же глобальна, как и любая другая глобальная. Вы можете управлять доступом к состоянию, но это может быть достигнуто разными способами, например, путем включения функций и состояния в его собственную единицу перевода с заголовком, который предлагает только операции и переменные, объявленные внутри static или внутри < i>безымянное пространство имен. Это имеет то преимущество, что действительно скрывает детали реализации: если вы измените эти переменные, вам нужно будет только скомпилировать свою собственную единицу перевода, поскольку они больше нигде не видны/не включаются. - person David Rodríguez - dribeas; 05.08.2011

Я использую struct со статическими функциями, потому что она предлагает немного лучшую изоляцию, чем функции, не являющиеся членами в пространствах имен (из-за отсутствия поиска Кенига).

Чтобы привести пример того, чего я хочу избежать:

namespace Foo
{
      struct A
      {
      };

      void f(const A& a) {}
}

void f(const Foo:A& a) { std::cout << "AAA"; }

int main(void)
{
      Foo::A a;
      f(a); // calls Foo:f
      return 0;
}
person quant_dev    schedule 05.08.2011
comment
Я использую бесплатные функции, потому что поиск Кенига существует. - person Alexandre C.; 05.08.2011
comment
Зависит от того, что вы хотите сделать. - person quant_dev; 05.08.2011
comment
Я готов принять вашу точку зрения, если вы можете предоставить мне пример, где 1) поиск Кенига вредит вам 2) тот факт, что поиск Кенига может вам навредить, не является запахом дизайна. Я не вижу, в чем проблема: в конце концов, поиск Кенига обычно является преимуществом. - person Alexandre C.; 05.08.2011
comment
Что ж, мне придется побеспокоиться о вашей готовности сделать то или иное. Чем я не являюсь ;-) - person quant_dev; 05.08.2011
comment
это было просто предложение улучшить ваше краткое заявление. Что касается вашего фактического примера, написав void f(const A& a), вы признаете, что f является частью интерфейса A, и я рассматриваю как особенность тот факт, что Foo::f вызывается вместо f из глобального пространства имен (вы не не помещайте ничего в глобальное пространство имен, не так ли?). Я не вижу, где должна находиться потенциальная двусмысленность. - person Alexandre C.; 05.08.2011