我应该在哪里放置与类无关的函数?


47

我正在一个C ++项目中工作,其中有很多数学函数,这些函数最初是作为类的一部分编写的。但是,由于我一直在编写更多代码,所以我意识到我到处都需要这些数学函数。

放置它们的最佳位置在哪里?假设我有这个:

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

当我写另一个类时,我不能(或者说我不知道​​如何)math_function1在那个其他类中使用它。另外,我已经意识到其中一些功能与A类并没有真正的联系。它们似乎只是开始,但现在我可以看到它们仅仅是数学功能。

在这种情况下有什么好的做法?现在,我一直在将它们复制粘贴到新类中,我敢肯定这是最糟糕的做法。


11
您是否了解static关键字?
S.Lott

30
在C ++中,自由函数几乎总是比成员函数更受青睐。
Pubby 2012年

4
没有规则说一切都必须在一个类中。至少在C ++中没有。
tdammers

2
我更喜欢名称空间而不是带有一堆静态方法的类
Nick Keighley

Answers:


70

C ++可以具有非方法函数,如果它们不属于类,则不要将它们放在类中,而应将它们放在全局或其他命名空间范围内

namespace special_math_functions //optional
{
    int math_function1(int arg)
    {
         //definition 
    }
}

6
+1这是最明智的解决方案,尽管似乎不需要额外的名称空间。
Pubby 2012年

1
不,它是没有必要的
JK。

27
一些名称空间对于减少与其他库的潜在名称冲突很有用。
Bill Door

11
使用名称空间也很不错,因为它可以消除调用是方法还是函数的歧义。(math_function1(42)可能会调用当前类的成员;special_math_functions::math_function1(42)显然是在调用独立函数)。话虽如此,但::math_function(42)提供了同样的歧义。
ipeet 2012年

2
命名空间不是必需的,但也不是禁止的。因此,为什么这个答案说// optional。根据口味调味。
user253751

6

取决于严格地是实用程序代码,您可以根据以下方式选择项目的组织方式和所使用的设计模式:

  • 如果您不必使用对象的所有功能,则可以做一些简单的事情,例如将它们全部放入文件中,而无需使用类包装器。尽管建议使用名称空间以防止将来出现任何问题,但可以使用或不使用名称空间。
  • 对于托管C ++,您可以创建一个静态类来包含所有这些。但是,这与实际的类并没有真正的作用,我的理解是它是C ++的反模式。
  • 如果您不使用托管C ++,则可以利用静态函数来访问它们,并将它们全部包含在一个类中。如果还有其他功能,您希望适当的实例化对象也可能是反模式,则这可能很有用。
  • 如果要确保仅存在包含函数的对象的一个​​实例,则可以将Singleton Pattern用于实用程序类,该类在将来也可以为您提供一些灵活性,因为您现在可以访问非静态属性。这将有限地使用,并且仅在由于某种原因需要对象时才真正适用。奇怪的是,如果您这样做,您将已经知道为什么。

请注意,第一个选项将是最好的选择,而后三个选项的作用有限。就是说,您可能会因为C#或Java程序员从事某些C ++工作,或者如果您曾经在C#或Java代码上工作而必须使用类而遇到这种情况。


为什么要下票?
rjzii 2012年

10
我不是拒绝这样做的人,但是可能是因为您建议使用静态函数或单例的类,而在这种情况下,自由函数可能会很好(并且对于C ++中的许多事情都是可接受的且有用的)。
Anton Golov

@AntonGolov-自由功能是我在列表中提到的第一件事。:)对于要处理“一切都必须是一个类!”的情况,其余方法都是面向OOP的方法。环境。
rjzii 2012年

9
@Rob Z:但是,C ++并不是其中的“一切都必须是一个类!”之一。环境。
David Thornley,2012年

1
从什么时候开始OOP将纯函数强制为类?似乎更像是面向对象的货物崇拜。
Deduplicator

1

正如您已经说过的,复制粘贴代码是代码重用的最糟糕形式。如果您的函数不属于您的任何类,或者可能用于多种情况,则放置它们的最佳位置是助手或实用程序类。如果它们不使用任何实例数据,则可以将它们设置为静态,因此您无需创建实用程序类的实例即可使用它。

这里为在本地C ++静态成员函数的讨论,并在这里对托管C ++静态类。然后,您可以在粘贴代码的任何地方使用此实用工具类。

例如,在.NET中,诸如Min()Max()这样的东西作为System.Math类的静态成员提供。

如果所有的功能都与数学有关的,你会otherwiese有一个巨大的Math类,您可能想进一步分解,并具有类一样TrigonometryUtilitiesEucledianGeometryUtilities等等。

另一种选择是将共享功能放入需要所述功能的类的基类中。当问题中的函数需要对实例数据进行操作时,这种方法很好用,但是,如果您要避免多重继承而只坚持一个基类,则这种方法的灵活性也较差,因为您将“用完”一个基类类只是为了访问某些共享功能。


18
恕我直言,只有静态成员的实用程序类是C ++中的反模式。您正在使用一个类来完美地再现名称空间的行为,这实际上没有任何意义。
ipeet 2012年

+1表示实用程序类。像C#这样的语言要求所有内容都在一个类中,因此为各种目的创建许多实用程序类是很常见的。将这些类实现为Static可以使实用程序更加用户友好,并且避免了有时可能会引起继承的麻烦,尤其是当基类被仅由一两个后代使用的代码code肿时。可以在其他语言中应用类似的技术,以为您的实用程序功能提供有意义的上下文,而不是将它们放在全局范围内。
S.Robins '02

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::coutstd::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){
 ..
}

然后,您还可以通过为自定义类型重载相关运算符以使它们易于使用,来创建表示例如大数或复数的整洁的自定义类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.