Пространство имен + функции против статических методов в классе

290

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

  1. Напишите эти функции и поместите их в мое MyMathпространство имен и обратитесь к ним черезMyMath::XYZ()
  2. Создайте класс с именем MyMathи сделайте эти методы статичными и обращайтесь к аналогичнымMyMath::XYZ()

Почему я выбрал одно из другого в качестве средства организации своего программного обеспечения?

RobertL
источник
4
с одной стороны, пространства имен являются более недавним дополнением к языку, по сравнению с классами и статическими методами, которые были в языке со времени, когда он назывался «C с классами». Некоторым программистам может быть удобнее использовать старые функции. Некоторые другие программисты могут использовать старые компиляторы. Просто мои $ .02
Rom
21
@Rom: Вы правы насчет «старых программистов», но ошибаетесь в отношении «старых компиляторов». Пространства имен правильно компилируются с незапамятных времен (я работал с ними с Visual C ++ 6, начиная с 1998 года!). Что касается «C с классами», некоторые люди на этом форуме даже не родились, когда это произошло: использовать это в качестве аргумента, чтобы избежать стандартной и широко распространенной функции C ++, является ошибкой. В заключение, только устаревшие компиляторы C ++ не поддерживают пространства имен. Не используйте этот аргумент как оправдание, чтобы не использовать их.
paercebal
@paercebal: некоторые древние компиляторы все еще используются во встроенном мире. Отсутствие поддержки пространств имен - это, вероятно, одно из самых маленьких неудобств, с которыми приходится мириться при написании кода для различных небольших процессоров, с которыми каждый день взаимодействует: стерео, микроволновая печь, блок управления двигателем в автомобиле, светофор и т. Д. быть ясным: я не защищаю для того, чтобы не использовать лучшие, более новые компиляторы везде. Au conrare: я все для новейших языковых функций (кроме RTTI;)). Я просто указываю на то, что такая тенденция существует
Рим,
13
@Rom: В текущем случае у автора вопроса есть выбор, поэтому, очевидно, ни один из его / ее компиляторов не сможет скомпилировать код в пространстве имен. И поскольку это вопрос о C ++, необходимо дать ответ на C ++, включая упоминание пространств имен и RTTI-решений проблемы, если это необходимо. Предоставление ответа C или ответа C-with-classes-for-устаревшие-compilers не соответствует теме.
paercebal
2
«Только мои $ .02» - было бы дороже, если бы вы предоставили какие-либо доказательства существующих компиляторов C ++, которые не поддерживают пространства имен. «некоторые древние компиляторы все еще используются во встроенном мире» - это глупо; «встроенный мир» является более поздней разработкой, чем пространства имен C ++. «C с классами» был введен в 1979 году, задолго до того, как кто-то встраивал C-код во что-либо. «Я просто указываю на то, что такая тенденция существует» - даже если это так, это не имеет никакого отношения к этому вопросу.
Джим Балтер

Ответы:

243

По умолчанию используйте функции пространства имен.

Классы предназначены для создания объектов, а не для замены пространств имен.

В объектно-ориентированном коде

Скотт Мейерс написал целую статью для своей книги «Эффективное C ++» на эту тему: «Предпочитайте функции, не являющиеся членами, не являющимися друзьями, функциям-членам». Я нашел онлайн ссылку на этот принцип в статье Херба Саттера:http://www.gotw.ca/gotw/084.htm

Важно знать следующее: в C ++ функции в том же пространстве имен, что и класс, принадлежат интерфейсу этого класса (потому что ADL будет искать эти функции при разрешении вызовов функций).

Функции пространств имен, если они не объявлены «другом», не имеют доступа к внутренним объектам класса, в то время как статические методы имеют.

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

Расширение I

Добавление кода в интерфейс класса.

В C # вы можете добавлять методы в класс, даже если у вас нет к нему доступа. Но в C ++ это невозможно.

Но все же в C ++ вы все еще можете добавить функцию пространства имен даже в класс, который кто-то написал для вас.

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

Расширение II

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

Для пространств имен функции из одного и того же пространства имен могут быть объявлены в нескольких заголовках (лучшим примером является почти стандартная функция подкачки).

Расширение III

Основная крутость пространства имен заключается в том, что в некотором коде вы можете не упоминать его, если используете ключевое слово «using»:

#include <string>
#include <vector>

// Etc.
{
   using namespace std ;
   // Now, everything from std is accessible without qualification
   string s ; // Ok
   vector v ; // Ok
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

И вы даже можете ограничить «загрязнение» одним классом:

#include <string>
#include <vector>

{
   using std::string ;
   string s ; // Ok
   vector v ; // COMPILATION ERROR
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

Этот «шаблон» является обязательным для правильного использования почти стандартной идиомы подкачки.

И это невозможно сделать со статическими методами в классах.

Итак, пространства имен C ++ имеют свою семантику.

Но это идет дальше, так как вы можете комбинировать пространства имен способом, похожим на наследование.

Например, если у вас есть пространство имен A с функцией AAA, пространство имен B с функцией BBB, вы можете объявить пространство имен C и ввести AAA и BBB в это пространство имен с помощью ключевого слова, используя.

Вывод

Пространства имен предназначены для пространств имен. Занятия для занятий.

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

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

И в вашем случае вам нужны пространства имен.

paercebal
источник
Можно ли применить этот ответ и к потокам, т. Е. Лучше ли использовать пространства имен, а не статические методы для потоков?
Дашесы
3
@dashesy: пространства имен и статические методы не имеют ничего общего с потоками, так что да, пространства имен лучше, потому что пространства имен почти всегда лучше, чем статические методы. Если одно, статические методы имеют доступ к переменным-членам класса, поэтому они каким-то образом имеют меньшее значение инкапсуляции, чем пространства имен. И изоляция данных еще важнее при многопоточном выполнении.
paercebal
@ paercebal - спасибо, я использовал статические методы класса для потоковых функций. Теперь я понимаю, что неправильно использовал класс в качестве пространства имен, так что, по вашему мнению, лучший способ иметь несколько потоков в одном объекте? Я тоже задал этот вопрос на SO, я ценю, если вы можете пролить немного света (здесь или в самом вопросе)
dashesy
1
@dashesy: ​​вы просите неприятностей. В разных потоках вы хотите изолировать данные, которые не предназначены для совместного использования, поэтому иметь несколько потоков с привилегированным доступом к закрытым данным класса - плохая идея. Я бы спрятал один поток внутри класса и обязательно изолировал данные этого потока от данных основного потока. Конечно, данные, которые должны совместно использоваться, могут быть членами этого класса, но они все равно должны быть синхронизированы (блокировки, атомарные и т. Д.). Я не уверен насчет того, к какому количеству у вас есть доступ к libs, но использование tasks / async еще лучше.
paercebal
Ответ paercebal должен быть принят! Еще одна ссылка для почти стандартного свопа () через пространство имен + ADL -> stackoverflow.com/questions/6380862/…
Gob00st
54

Есть много людей, которые не согласятся со мной, но вот как я это вижу:

Класс - это, по сути, определение определенного вида объекта. Статические методы должны определять операции, которые тесно связаны с определением этого объекта.

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

Например, в вашем случае спросите себя: «Что такое MyMath?» Если MyMathне определить вид объекта, я бы сказал: не делайте его классом.

Но, как я уже сказал, я знаю, что есть много людей, которые (даже яростно) не согласны со мной в этом (в частности, разработчики на Java и C #).

Дэн Тао
источник
3
У вас очень чистый взгляд на это. Но, фактически, класс с полностью статическими методами может пригодиться: вы можете typedefих использовать, использовать их в качестве параметров шаблона и т. Д.
Shog9
56
Это потому, что у Jave и C # людей нет выбора.
Мартин Йорк,
7
@ shog9. Вы также можете шаблонизировать функции!
Мартин Йорк,
6
@Dan: по-видимому, тот, который нуждался в математических процедурах и хотел поддерживать «включение» различных реализаций.
Shog9
1
@Dan: «Я думаю, что если кто-то заинтересован в использовании класса в качестве параметра шаблона, этот класс почти наверняка определит некоторый базовый объект». Нет, совсем нет. Подумайте о чертах. (Тем не менее, я полностью согласен с вашим ответом.)
sbi
18
  • Если вам нужны статические данные, используйте статические методы.
  • Если они являются шаблонными функциями, и вы хотите иметь возможность указать набор параметров шаблона для всех функций вместе, используйте статические методы в классе шаблона.

В противном случае используйте функции пространства имен.


В ответ на комментарии: да, статические методы и статические данные имеют тенденцию к чрезмерному использованию. Вот почему я предложил только два взаимосвязанных сценария, где я думаю, что они могут быть полезны. В конкретном примере OP (набор математических подпрограмм), если он хочет иметь возможность задавать параметры - скажем, основной тип данных и точность вывода - которые будут применяться ко всем подпрограммам, он может сделать что-то вроде:

template<typename T, int decimalPlaces>
class MyMath
{
   // routines operate on datatype T, preserving at least decimalPlaces precision
};

// math routines for manufacturing calculations
typedef MyMath<double, 4> CAMMath;
// math routines for on-screen displays
typedef MyMath<float, 2> PreviewMath;

Если вам это не нужно, то все средства использовать пространство имен.

Shog9
источник
2
так называемые статические данные могут быть данными уровня пространства имен в файле реализации пространства имен, это еще больше уменьшает связь, так как они не должны отображаться в заголовке.
Мотти
Статические данные не лучше глобальных пространств имен.
Копро
@coppro. Они, по крайней мере, на один шаг вверх по эволюционной цепочке от случайных глобалов, так как они могут быть сделаны частными (но в остальном согласны).
Мартин Йорк,
@Motti: OTOH, если вы хотите это в заголовке (встроенные / шаблонные функции), вы вернетесь к тому, чтобы быть уродливым.
Shog9
1
Интересный пример, использование класса в качестве сокращения, чтобы избежать повторения templateаргументов!
underscore_d
13

Вы должны использовать пространство имен, потому что пространство имен имеет много преимуществ перед классом:

  • Вам не нужно определять все в одном заголовке
  • Вам не нужно выставлять всю вашу реализацию в шапке
  • Вы не можете usingученик; Вы можете usingчлен пространства имен
  • Вы не можете using class, хотя using namespaceне все так часто хорошая идея
  • Использование класса подразумевает, что есть некоторый объект, который нужно создать, когда его нет

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

coppro
источник
3
«Вам не нужно показывать всю свою реализацию в заголовке», также как и при использовании класса.
Вануан
Более того: если вы используете пространства имен, вы не можете представить всю свою реализацию в заголовке (у вас будет несколько определений символов). Встроенные функции класса позволяют вам это делать.
Вануан
1
@Vanuan: Вы можете представить реализации пространства имен в заголовке. Просто используйте inlineключевое слово, чтобы удовлетворить ODR.
Томас Эдинг
@ThomasEding не нужно! = Можно
Вануан
3
@Vanuan: Компилятором гарантируется только одна вещь inline, и она НЕ "встраивает" тело функции. Реальная (и гарантирован стандарт) цель inlineсостоит в предотвращении нескольких определений. Читайте об «одном правиле определения» для C ++. Кроме того, связанный вопрос SO не компилировался из-за проблем с предварительно скомпилированными заголовками вместо проблем ODR.
Томас Эдинг
3

Я бы предпочел пространства имен, таким образом, вы можете хранить личные данные в анонимном пространстве имен в файле реализации (поэтому он не должен отображаться в заголовке вообще, в отличие от privateчленов). Другое преимущество заключается в том, что usingвашими именами клиенты могут отказаться от указания методов.MyMath::

Моти
источник
2
Вы также можете хранить личные данные в анонимном пространстве имен в файле реализации с классами. Не уверен, что я следую вашей логике.
Патрик Джонмейер
0

Еще одна причина использовать класс - Возможность использовать спецификаторы доступа. Затем вы можете разбить ваш публичный статический метод на меньшие приватные методы. Открытый метод может вызывать несколько частных методов.

Милинд Т
источник
6
Модификаторы доступа - это круто, но даже самый privateметод является более доступным, чем метод, прототип которого вообще не публикуется в заголовке (и, следовательно, остается невидимым). Я даже не упоминаю о лучшей инкапсуляции, предлагаемой анонимно распределенными функциями.
paercebal
2
Частные методы, IMO, уступают скрытию самой функции в реализации (файл cpp) и никогда не предоставляют ее в заголовочном файле. Пожалуйста, уточните это в своем ответе и почему вы бы предпочли использовать частных пользователей . До тех пор -1.
безобидный
@nonsensickle Возможно, он имеет в виду, что функцию мамонта со множеством повторяющихся разделов можно безопасно разбить, скрывая оскорбительные подразделы за частным, мешая другим получить к ним доступ, если они опасны / требуют очень осторожного использования.
Тройсеф
1
@Troyseph, тем не менее, вы можете скрыть эту информацию в безымянном пространстве имен в .cppфайле, что сделало бы его приватным для этого модуля перевода, не предоставляя никакой лишней информации любому, кто читает файл заголовка. По сути, я пытаюсь отстаивать идиому PIMPL.
невосприимчивый
Вы не можете поместить его в .cppфайл, если хотите использовать шаблоны.
Yay295
0

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

rocd
источник
«[хранение вещей] в анонимном пространстве имен файла реализации [является] большей областью, чем хранение их внутри класса» - нет, это не так. в тех случаях, когда привилегированный доступ к членам не требуется, анонимно распределенные по именам вещи являются более конфиденциальными, чем private:те. и во многих случаях , когда привилегированный доступ , кажется , потребуется, что может быть вынесено. самая частная функция - это функция, которая не появляется в заголовке. private:методы никогда не могут пользоваться этим преимуществом.
underscore_d