如何在C ++中创建静态类?


263

如何在C ++中创建静态类?我应该能够做类似的事情:

cout << "bit 5 is " << BitParser::getBitAt(buffer, 5) << endl;

假设我创建了BitParser该类。会是什么BitParser类的定义是什么样子?


198
是的 在过去,我们只是称其为“功能”。今天,您可以通过疯狂的“命名空间”来哄孩子。嘿,下车我的草坪!<shakes fist>
游荡的2010年

7
@Vagrant名称空间内的函数仍然是一个函数。属于类的函数称为方法。如果它是静态方法,则调用它的方式类似于命名空间中的函数。

48
5年后的现在,我和@Vagrant在一起。真是个愚蠢的问题!
andrewrk

16
9年后的现在,我有了自己的甚至没有课程的编程语言:ziglang.org
andrewrk

1
在某些情况下,类似于IMO容器的类(仅具有静态方法)很有用。
AarCee

Answers:


272

如果您正在寻找一种将“ static”关键字应用于类的方法(例如在C#中),那么如果不使用Managed C ++,您将无法做到。

但是,从示例的外观来看,您只需要在BitParser对象上创建一个公共静态方法。像这样:

BitParser.h

class BitParser
{
 public:
  static bool getBitAt(int buffer, int bitIndex);

  // ...lots of great stuff

 private:
  // Disallow creating an instance of this object
  BitParser() {}
};

BitParser.cpp

bool BitParser::getBitAt(int buffer, int bitIndex)
{
  bool isBitSet = false;
  // .. determine if bit is set
  return isBitSet;
}

您可以使用此代码以与示例代码相同的方式调用方法。

希望有帮助!干杯。


2
OJ,您的语法错误。static关键字只能在类定义中使用,而不能在方法定义中使用。
andrewrk

90
为了使您的意图更清晰,您可以额外使用私有构造函数。private: BitParser() {}这将阻止任何人创建实例。
Danvil

7
共享状态时,@ MoatazElmasry线程安全是一个问题。在上述实现中,没有状态共享,因此线程安全不会有任何问题……除非您愚蠢到这些函数中使用静态变量。所以是的,上面的代码是线程安全的,只需将持久状态保留在您的函数之外,您就可以了。
OJ。

@MoatazElmasry不正确。两个线程不能在静态函数中修改非静态局部变量。
OJ。

12
如果是C ++ 11,我认为最好能BitParser() = delete;正确传达删除构造函数的意图(而不仅仅是将其隐藏为private)。
凤凰城'18

247

考虑马特·普赖斯(Matt Price)的解决方案

  1. 在C ++中,“静态类”没有意义。最接近的是只有静态方法和成员的类。
  2. 使用静态方法只会限制您。

您想要的是用C ++语义表示的,将您的函数(因为它一个函数)放在命名空间中。

编辑2011-11-11

C ++中没有“静态类”。最接近的概念是只有静态方法的类。例如:

// header
class MyClass
{
   public :
      static void myMethod() ;
} ;

// source
void MyClass::myMethod()
{
   // etc.
}

但您必须记住,“静态类”是类Java语言(例如C#)中的hack,它们无法具有非成员函数,因此它们必须将它们作为静态方法移入类中。

在C ++中,您真正想要的是将在命名空间中声明的非成员函数:

// header
namespace MyNamespace
{
   void myMethod() ;
}

// source
namespace MyNamespace
{
   void myMethod()
   {
      // etc.
   }
}

这是为什么?

在C ++中,名称空间比“ Java静态方法”模式的类更强大,因为:

  • 静态方法可以访问类的专用符号
  • 私有静态方法对所有人仍然可见(如果无法访问),这在某种程度上违反了封装
  • 静态方法不能被预先声明
  • 静态方法不能由类用户重载而无需修改库头
  • 在同一个名称空间中,静态方法无法完成比(可能是朋友)非成员函数更好的静态方法
  • 名称空间具有自己的语义(可以组合,可以是匿名的,等等)
  • 等等

结论:不要在C ++中复制/粘贴Java / C#的模式。在Java / C#中,该模式是强制性的。但是在C ++中,这是不好的样式。

编辑2010-06-10

有人赞成使用静态方法,因为有时需要使用静态私有成员变量。

我有些不同意,如下所示:

“静态私人会员”解决方案

// HPP

class Foo
{
   public :
      void barA() ;
   private :
      void barB() ;
      static std::string myGlobal ;
} ;

首先,myGlobal之所以称为myGlobal,是因为它仍然是全局私有变量。查看CPP来源将阐明:

// CPP
std::string Foo::myGlobal ; // You MUST declare it in a CPP

void Foo::barA()
{
   // I can access Foo::myGlobal
}

void Foo::barB()
{
   // I can access Foo::myGlobal, too
}

void barC()
{
   // I CAN'T access Foo::myGlobal !!!
}

乍一看,从封装的角度来看,自由函数barC无法访问Foo :: myGlobal似乎是一件好事……这很酷,因为某些人在查看HPP时将无法(除非进行破坏活动)访问。 Foo :: myGlobal。

但是,如果仔细观察,您会发现这是一个巨大的错误:不仅必须在HPP中声明您的私有变量(因此,尽管是私有的,但对全世界都是可见的),而且还必须声明在同一HPP中,所有(如在ALL中)将被授权访问它的功能!

因此,使用私人静态成员就像裸照在外面走动,在皮肤上刺上您的恋人名单:没有人可以触摸,但每个人都可以窥视。好处是:每个人都可以拥有被授权与您的私人玩耍的人的名字。

private 确实... :-D

“匿名名称空间”解决方案

匿名名称空间将具有使事物变为真正私有的优势。

一,HPP头

// HPP

namespace Foo
{
   void barA() ;
}

只是要确保您说了一下:barB或myGlobal没有无用的声明。这意味着没有人阅读标头会知道barA后面隐藏的内容。

然后,CPP:

// CPP
namespace Foo
{
   namespace
   {
      std::string myGlobal ;

      void Foo::barB()
      {
         // I can access Foo::myGlobal
      }
   }

   void barA()
   {
      // I can access myGlobal, too
   }
}

void barC()
{
   // I STILL CAN'T access myGlobal !!!
}

如您所见,就像所谓的“静态类”声明一样,fooA和fooB仍然可以访问myGlobal。但是没有人能做到。CPP之外的其他人都不知道fooB和myGlobal甚至存在!

不像“静态类”裸体地走着,地址簿上刻着她的皮肤,而“匿名”名称空间则衣冠楚楚,似乎封装了更好的AFAIK。

真的有关系吗?

除非您的代码的用户是破坏者(作为练习,我将让您了解如何使用肮脏的行为未定义的hack访问公共类的私有部分...),那private是什么private,即使它private在标头中声明的类的部分中可见。

不过,如果您需要添加另一个可以访问私有成员的“私有函数”,则仍然必须通过修改标头向全世界进行声明,就我所知,这是一个悖论:如果我更改了我的代码(CPP部分),则界面(HPP部分)不应更改。引用利奥尼达斯的话:“ 这就是包囊!

编辑2014-09-20

什么时候类静态方法实际上比具有非成员函数的名称空间更好?

当您需要将功能分组在一起并将该分组输入模板时:

namespace alpha
{
   void foo() ;
   void bar() ;
}

struct Beta
{
   static void foo() ;
   static void bar() ;
};

template <typename T>
struct Gamma
{
   void foobar()
   {
      T::foo() ;
      T::bar() ;
   }
};

Gamma<alpha> ga ; // compilation error
Gamma<Beta> gb ;  // ok
gb.foobar() ;     // ok !!!

因为,如果类可以作为模板参数,则名称空间就不能。


3
GCC支持-fno-access-control,可在白盒单元测试中使用它来访问其他私有类成员。这就是我可以想到的在实现中使用类成员而不是匿名/静态全局变量来证明其唯一原因的原因。
汤姆(Tom)2010年

8
@Tom:跨平台的解决方案是#define private public在标题中添加以下代码... ^ _ ^ ...
paercebal 2010年

1
@Tom:无论如何,恕我直言,即使考虑进行单元测试,“显示太多内容”的弊端也超过了优点。我猜一个替代的解决方案是将要测试的代码放在一个函数中,该函数采用utilities名称空间中所需的参数(不再需要)。这样,此功能可以进行单元测试,并且仍然没有对私有成员的特殊访问权限(因为它们是在函数调用时作为参数给出的)...
paercebal 2010年

@paercebal我要登上你的船,但我有最后的保留。如果有人跳入您的行列,namespace他们是否将无法访问您的global成员(尽管隐藏)?显然,他们将不得不猜测,但是除非您有意混淆代码,否则变量名称非常容易猜测。
扎克2014年

@Zak:的确可以,但是只能尝试在声明myGlobal变量的CPP文件中执行此操作。关键是可见性胜于可访问性。在静态类中,myGlobal变量是私有的,但仍可见。这似乎并不重要,但在DLL中,在导出的标头中显示应为DLL专用的符号可能会很尴尬...在命名空间中,myGlobal仅存在于CPP文件中(您可以甚至可以走得更远并使其静止)。该变量不会出现在公共标题中。
paercebal 2014年

63

您还可以在名称空间中创建一个自由函数:

在BitParser.h中

namespace BitParser
{
    bool getBitAt(int buffer, int bitIndex);
}

在BitParser.cpp中

namespace BitParser
{
    bool getBitAt(int buffer, int bitIndex)
    {
        //get the bit :)
    }
}

通常,这是编写代码的首选方式。当不需要对象时,不要使用类。


1
在某些情况下,即使该类大多是“静态”的,您可能仍希望进行数据封装。静态私人班级成员将为您提供此服务。命名空间成员始终是公共的,不能提供数据封装。
Torleif

如果仅从.cpp文件声明和访问“成员” var,则它比.h文件中声明的私有var更私有。并不是我推荐这种技术。
jmucchiello

3
@Torleif:你错了。命名空间比静态私有成员更适合封装。请参阅我的答案进行演示。
paercebal 2010年

1
是的,但是在命名空间中,与具有静态成员的类相比,您必须保持函数顺序,例如void a(){b();} b(){}会在命名空间中产生错误,但在具有以下内容的类中不会产生错误静态成员
Moataz Elmasry 2012年

13

如果您正在寻找一种将“ static”关键字应用于类的方法,例如在C#中

静态类只是编译器,它会牵着您的手,阻止您编写任何实例方法/变量。

如果您只是编写一个没有任何实例方法/变量的普通类,那是同一回事,这就是您在C ++中要做的


不要抱怨(尤其是对您),但是一些编译器的手工操作可以阻止我写或剪切/粘贴该单词static200次,这是一件好事。
3Dave

同意-但是C#中的静态类也不执行此操作。当您忘记在其中粘贴静电时,它只是无法编译:-)
Orion Edwards

是的-很公平。我的宏正在显示。老实说,如果我将该类声明为静态类,则编译器仅在尝试实例化它时才抛出错误。要求我重复自己的规则是令人讨厌的,当革命来临时,这些规则应该是第一个碰壁的规则。
3Dave

11

在C ++中,您要创建类的静态函数(而不是静态类)。

class BitParser {
public:
  ...
  static ... getBitAt(...) {
  }
};

然后,您应该能够使用BitParser :: getBitAt()调用该函数,而无需实例化一个我认为是所需结果的对象。


11

我可以写类似的东西static class吗?

,根据C ++ 11 N3337标准草案附件C 7.1.1:

更改:在C ++中,静态或外部说明符只能应用于对象或函数的名称。在C ++中,将这些说明符与类型声明一起使用是非法的。在C语言中,将这些说明符用于类型声明时将被忽略。例:

static struct S {    // valid C, invalid in C++
  int i;
};

原理:存储类说明符与类型关联时没有任何意义。在C ++中,可以使用静态存储类说明符声明类成员。允许在类型声明上使用存储类说明符可能会使用户感到困惑。

和一样structclass也是类型声明。

可以通过遍历附件A中的语法树来推断出相同的含义。

有趣的是,这static struct在C语言中是合法的,但没有效果:为什么以及何时在C语言编程中使用静态结构?


6

正如此处已指出的那样,在C ++中实现此目标的更好方法可能是使用名称空间。但是由于这里没有人提到final关键字,因此我要发布static class在C ++ 11或更高版本中与C#直接等效的内容:

class BitParser final
{
public:
  BitParser() = delete;

  static bool GetBitAt(int buffer, int pos);
};

bool BitParser::GetBitAt(int buffer, int pos)
{
  // your code
}

5

如前所述,您可以在C ++中拥有一个静态类,静态类是一个没有实例化它的对象的类。在C ++中,这可以通过将构造函数/析构函数声明为私有来获得。最终结果是相同的。


您所建议的可能会创建一个单例类,但它与静态类不同。
ksinkar

4

在托管C ++中,静态类语法为:

public ref class BitParser abstract sealed
{
    public:
        static bool GetBitAt(...)
        {
            ...
        }
}

... 迟到总比不到好...


3

这类似于C#在C ++中的实现方式

在C#file.cs中,您可以在公共函数中包含私有变量。在另一个文件中时,可以通过使用以下函数调用名称空间来使用它:

MyNamespace.Function(blah);

这是在C ++中实现相同效果的方法:

共享模块

class TheDataToBeHidden
{
  public:
    static int _var1;
    static int _var2;
};

namespace SharedData
{
  void SetError(const char *Message, const char *Title);
  void DisplayError(void);
}

共享模块

//Init the data (Link error if not done)
int TheDataToBeHidden::_var1 = 0;
int TheDataToBeHidden::_var2 = 0;


//Implement the namespace
namespace SharedData
{
  void SetError(const char *Message, const char *Title)
  {
    //blah using TheDataToBeHidden::_var1, etc
  }

  void DisplayError(void)
  {
    //blah
  }
}

OtherFile.h

#include "SharedModule.h"

OtherFile.cpp

//Call the functions using the hidden variables
SharedData::SetError("Hello", "World");
SharedData::DisplayError();

2
但是每个人都可以去TheDataToBeHidden->这不是解决方案
Guy L


0

命名空间对于实现“静态类”可能不那么有用的一种情况是使用这些类实现继承的组合。命名空间不能成为类的朋友,因此不能访问类的私有成员。

class Class {
 public:
  void foo() { Static::bar(*this); }    

 private:
  int member{0};
  friend class Static;
};    

class Static {
 public:
  template <typename T>
  static void bar(T& t) {
    t.member = 1;
  }
};

0

一种选择(但有很多选择),但最优雅(在我看来)(与使用名称空间和私有构造函数模拟静态行为相比),在C ++中实现“无法实例化的类”行为的方法是:使用privateaccess修饰符声明一个伪纯虚函数。

class Foo {
   public:
     static int someMethod(int someArg);

   private:
     virtual void __dummy() = 0;
};

如果您使用的是C ++ 11,则可以通过final在类声明中使用说明符来限制其他类继承该类,以确保不继承该类(以纯粹模拟静态类的行为)。。

// C++11 ONLY
class Foo final {
   public:
     static int someMethod(int someArg);

   private:
      virtual void __dummy() = 0;
};

尽管听起来很愚蠢和不合逻辑,但C ++ 11允许声明“无法覆盖的纯虚函数”,您可以将其与声明类一起使用,final以纯粹并完全实现静态行为,因为这会导致结果类不可继承,并且伪函数不会以任何方式被覆盖。

// C++11 ONLY
class Foo final {
   public:
     static int someMethod(int someArg);

   private:
     // Other private declarations

     virtual void __dummy() = 0 final;
}; // Foo now exhibits all the properties of a static class
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.