Где я должен поместить функции, которые не связаны с классом?

47

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

Где лучшее место для их размещения? Допустим, у меня есть это:

class A{
    public:
        int math_function1(int);
        ...
}

И когда я пишу другой класс, я не могу (или хотя бы не знаю, как) использовать это math_function1в этом другом классе. Кроме того, я понял, что некоторые из этих функций на самом деле не относятся к классу А. Они, казалось, были в начале, но теперь я могу видеть, что они просто математические функции.

Какова хорошая практика в этой ситуации? Прямо сейчас я копирую их в новые классы, что, я уверен, является худшей практикой.

кокос
источник
11
Вы узнали о staticключевом слове?
S.Lott
31
В C ++ свободные функции почти всегда предпочтительнее функций-членов.
Пабби
4
Нет правила, согласно которому все должно быть в классе. По крайней мере, не в C ++.
tdammers
2
Я бы предпочел пространство имен классу с кучей статических методов
Ник Кейли

Ответы:

71

C ++ может иметь функции, не относящиеся к методам, просто отлично, если они не принадлежат классу, не помещайте их в класс, просто поместите их в глобальную или другую область пространства имен

namespace special_math_functions //optional
{
    int math_function1(int arg)
    {
         //definition 
    }
}
JK.
источник
6
+1 Это наиболее разумное решение, хотя дополнительное пространство имен не кажется необходимым.
Пабби
1
нет, это не нужно
JK.
27
Некоторое пространство имен полезно для уменьшения потенциальных конфликтов имен с другими библиотеками.
Билл Дверь
11
Использование пространства имен также хорошо, потому что оно однозначно определяет, является ли вызов методом или функцией. ( math_function1(42)может вызывать член текущего класса; special_math_functions::math_function1(42)явно вызывает независимую функцию). Это сказанное, ::math_function(42)обеспечивает то же самое неоднозначность.
ipeet
2
Пространства имен не нужны, но они также не запрещены. Следовательно, почему этот ответ говорит // optional. Сезон по вкусу.
user253751
6

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

  • Если вам не нужно использовать объекты для всего, вы можете сделать что-то простое, например просто поместить их все в файл без обертки классов вокруг них. Это может быть с или без пространства имен, хотя пространство имен рекомендуется для предотвращения любых проблем в будущем.
  • Для управляемого C ++ вы можете создать статический класс, содержащий их все; однако, это на самом деле не работает так же, как реальный класс, и я понимаю, что это анти-паттерн C ++.
  • Если вы не используете управляемый C ++, вы можете просто использовать статические функции, чтобы позволить вам получить к ним доступ и хранить их все в одном классе. Это может быть полезно, если есть и другие функции, для которых вы бы хотели, чтобы объект был создан надлежащим образом, а также может быть анти-паттерном.
  • Если вы хотите обеспечить существование только одного экземпляра объекта, содержащего функции, вы можете использовать шаблон Singleton для служебного класса, который также дает вам некоторую гибкость в будущем, поскольку теперь у вас есть доступ к нестатическим атрибутам. Это будет ограниченное использование и действительно применимо, только если вам нужен объект по какой-то причине. Скорее всего, если вы сделаете это, вы уже будете знать, почему.

Обратите внимание, что первый вариант будет лучшей ставкой, а следующие три имеют ограниченную полезность. Тем не менее, вы можете столкнуться с этим просто из-за того, что программисты на C # или Java выполняют некоторую работу на C ++, или если вы когда-либо работаете над кодом на C # или Java, где использование классов является обязательным.

rjzii
источник
Почему отрицательный голос?
rjzii
10
Я не downvoter, но, вероятно, потому что вы советуете класс со статическими функциями или singleton, в то время как свободные функции, вероятно, будет хорошо в этом случае (и приемлемы и полезны для многих вещей в C ++).
Антон Голов
@AntonGolov - бесплатные функции - это первое, что я упомянул в списке. :) Остальные - это ООП-ориентированные подходы для ситуаций, когда вы имеете дело с «Все должно быть классом!» сред.
rjzii
9
@Rob Z: Тем не менее, C ++ не является одним из тех, "Все должно быть классом!" сред.
Дэвид Торнли
1
С каких это пор ООП принудительно вводить чистые функции в класс? Больше похоже на ООП-карго-культ.
Дедупликатор
1

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

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

Например, в .NET такие вещи, как Min()и Max()предоставляются как статические члены System.Mathкласса .

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

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

PersonalNexus
источник
18
ИМХО, служебные классы, в которых нет ничего, кроме статических членов, являются антишаблоном в C ++. Вы используете класс, чтобы идеально воспроизвести поведение пространства имен, что на самом деле не имеет смысла.
ipeet
+1 за упоминание служебных классов. Такие языки, как C #, требуют, чтобы все было в классе, поэтому довольно часто можно создавать несколько служебных классов для различных целей. Реализация этих классов как статических делает утилиты еще более удобными для пользователя и позволяет избежать головной боли, которую иногда может создавать наследование, особенно когда базовые классы становятся раздутыми с кодом, который может использоваться только одним или двумя потомками. Подобные методы могут применяться на других языках, чтобы обеспечить значимый контекст для ваших служебных функций, а не оставлять их плавающими в глобальной области видимости.
С.Робинс
5
@ S.Robins: В C ++ ничего подобного не нужно, вы можете просто поместить их в пространство имен, что дает точно такой же эффект.
DeadMG
0

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

// a general helper 
template <class T>
bool isPrinter(T& p){
   return (dynamic_cast<Printer>(p))? true: false;
}

    // specific helper for printers
namespace printer_utils {    
  namespace HP {
     print_alignment_page() { printAlignPage();}
  }

  namespace Xerox {
     print_alignment_page() { Alignment_Page_Print();}
  }

  namespace Canon {
     print_alignment_page() { AlignPage();}
  }

   namespace Kyocera {
     print_alignment_page() { Align(137,4);}
   }

   namespace Panasonic {
      print_alignment_page() { exec(0xFF03); }
   }
} //namespace

Теперь isPrinterдоступен любой код, включая его заголовок, но print_alignment_pageтребует using namespace printer_utils::Xerox;директивы. Можно также сослаться на это как

Canon::print_alignment_page();

чтобы быть более понятным.

C ++ STL имеет std::пространство имен, которое охватывает почти все его классы и функции, но он разбивает их по категориям на более чем 17 различных заголовков, чтобы позволить кодировщику получить имена классов, имена функций и т. Д., Если они хотят написать их собственный.

Фактически, НЕ рекомендуется использовать using namespace std;в заголовочном файле или, как это часто делается, в качестве первой строки внутри main(). std::это 5 букв и часто кажется рутиной предисловия к функции, которую вы хотите использовать (особенно std::coutи std::endl!), но она служит цели.

В новом C ++ 11 есть несколько подпространств имен для специальных служб, таких как

std::placeholders,
std::string_literals,
std::chrono,
std::this_thread,
std::regex_constants

это может быть принесено для использования.

Полезной техникой является композиция пространства имен . Один определяет пользовательское пространство имен для хранения пространств имен, необходимых для вашего конкретного .cppфайла, и использует его вместо набора usingоператоров для каждой вещи в пространстве имен, которая может вам понадобиться.

#include <iostream>
#include <string>
#include <vector>

namespace Needed {
  using std::vector;
  using std::string;
  using std::cout;
  using std::endl;
}

int main(int argc, char* argv[])
{
  /*  using namespace std; */
      // would avoid all these individual using clauses,
      // but this way only these are included in the global
      // namespace.

 using namespace Needed;  // pulls in the composition

 vector<string> str_vec;

 string s("Now I have the namespace(s) I need,");

 string t("But not the ones I don't.");

 str_vec.push_back(s);
 str_vec.push_back(t);

 cout << s << "\n" << t << endl;
 // ...

Этот метод ограничивает доступ ко всему std:: namespace( он большой! ) И позволяет писать более чистый код для наиболее распространенных строк кода, которые люди пишут чаще всего.

Крис Рид
источник
-2

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

template <typename T>
T math_function1(T){
 ..
}

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

user1703394
источник