C ++에서 정적 클래스를 어떻게 작성합니까?


263

C ++에서 정적 클래스를 어떻게 작성합니까? 나는 다음과 같은 것을 할 수 있어야한다.

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

BitParser클래스를 만들었다 고 가정합니다 . 무엇 것 BitParser같은 클래스 정의 모양을?


198
네. 예전에는 방금 "기능"이라고했습니다. 당신은 오늘 당신의 미친 "네임 스페이스"를 가지고 있습니다. 이봐, 내 잔디밭을 벗어! <흔들리는 주먹>
유랑자

7
@ 네임 스페이스 내부의 함수는 여전히 함수입니다. 클래스에 속하는 함수를 메소드라고합니다. 정적 메서드 인 경우 네임 스페이스 내의 함수 인 것처럼 비슷하게 호출합니다.

48
5 년 후 지금은 @Vagrant와 함께 사이딩하고 있습니다. 어리석은 질문입니다!
andrewrk

16
9 년이 지난 지금 나는 수업이없는 나만의 프로그래밍 언어를 가지고있다 : ziglang.org
andrewrk

1
IMO 컨테이너와 유사한 클래스 (정적 메서드 만 있음)는 특정 경우에 유용합니다.
AarCee

답변:


272

예를 들어 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).
피닉스

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 #)의 해킹이므로 클래스 내에서 정적 메소드로 이동해야합니다.

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을 알고 있지 않습니다!

그녀의 피부에 문신을 한 그녀의 주소록으로 누드를 걷는 "정적 클래스"와는 달리, "익명"네임 스페이스는 완전히 옷을 입었습니다 .

정말 문제가 되나요?

코드의 사용자가 파괴 활동가가되지 않는 한, 무슨 것은 (나는 연습으로, 하나는 ... 더러운 행동 정의되지 않은 해킹을 사용하여 공용 클래스의 민간 부분에 액세스 할 수있는 방법을 찾아 당신을 드리겠습니다) private이며 private, 심지어 경우 private헤더에 선언 된 클래스 섹션 에서 볼 수 있습니다 .

: 당신은 개인 회원에 액세스 할 수있는 또 다른 "개인 기능"을 추가해야하는 경우 그러나, 당신은 아직도 내가 걱정하고 지금까지와 같은 역설 헤더, 수정하여 전 세계에 선언해야 내가의 구현을 변경하는 경우를 내 코드 (CPP 부분), 인터페이스 (HPP 부분)가 바뀌지 않아야합니다. 인용 Leonidos : " 이 캡슐화입니다! "

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

8
@Tom : 크로스 플랫폼 솔루션은 #define private public헤더에 다음 코드를 추가하는 것입니다 ... ^ _ ^ ...
paercebal

1
@Tom : 어쨌든, IMHO, 심지어 단위 테스트를 고려하더라도 "너무 많은 물건을 보여주는"단점은 전문가보다 비쌉니다. 나는 대체 솔루션이에 필요한 매개 변수를 (더 이상) 복용 기능을 테스트 할 수있는 코드를 넣어하는 것 같아요 utilities네임 스페이스를. 이런 식으로,이 함수는 단위 테스트를 할 수 있으며, 여전히 개인 멤버에 대한 특별한 액세스 권한이 없습니다 (함수 호출시 매개 변수로
제공됨

@paercebal 나는 당신의 배에 올라 타려고 해요. 그러나 나는 마지막 예약이 있습니다. 누군가 당신의 namespace의지에 뛰어 global들어도 숨겨져 있지만 회원에게 접근 할 수 없습니까? 분명히 그들은 추측해야하지만, 의도적으로 코드를 난독 화하지 않는 한 변수 이름은 추측하기 쉽습니다.
Zak

@Zak : 실제로, 그들은 myGlobal 변수가 선언 된 CPP 파일에서만 그렇게 할 수 있습니다. 요점은 접근성보다 가시성입니다. 정적 클래스에서 myGlobal 변수는 개인용이지만 여전히 표시됩니다. 이것은 보이는 것만 큼 중요하지는 않지만 여전히 DLL에서 내 보낸 헤더에 DLL에 대해 개인 기호를 표시하는 것이 어색 할 수 있습니다 ... 네임 스페이스에서 myGlobal은 CPP 파일에만 존재합니다. 더 멀리 가서 정적으로 만들 수도 있습니다). 해당 변수는 공개 헤더에 나타나지 않습니다.
paercebal

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

"member"var가 .cpp 파일에서만 선언되고 액세스되는 경우 .h 파일에 선언 된 private var보다 비공개입니다. 이 기술을 권장하지는 않습니다.
jmucchiello

3
@Torleif : 당신이 틀 렸습니다. 네임 스페이스는 정적 개인 멤버보다 캡슐화에 더 좋습니다. 데모에 대한 내 대답을 참조하십시오.
paercebal 2016 년

1
예. 네임 스페이스에서는 정적 멤버가있는 클래스와 달리 함수 순서를 유지해야합니다. 예를 들어 void a () {b ();} b () {}는 네임 스페이스에서 오류를 생성하지만 정적 회원
Moataz Elmasry

13

"정적"키워드를 클래스에 적용하는 방법을 찾고 있다면 C #에서와 같이

정적 클래스는 컴파일러를 손에 쥐고 인스턴스 메소드 / 변수를 작성하지 못하게합니다.

인스턴스 메소드 / 변수없이 일반 클래스를 작성하는 경우에도 마찬가지입니다. 이것은 C ++에서 수행하는 작업입니다.


(특히 당신에게) 불평하지는 않지만, 단어를 static200 번 쓰거나 잘라내거나 붙여 넣지 못하게하는 컴파일러 손을 잡는 것이 좋습니다.
3Dave

동의-그러나 C #의 정적 클래스는이 작업을 수행하지 않습니다. 그것은 당신이 :-) 거기에 정적을 붙여 넣기하는 것을 잊지 때 컴파일에 실패
오리온 에드워즈

예-충분합니다. 내 매크로가 표시됩니다. 솔직히 클래스를 정적으로 선언하면 인스턴스화하려고하면 컴파일러에서 오류가 발생해야합니다. 나 자신을 되풀이해야하는 규칙은 불쾌하며 혁명이 올 때 가장 먼저 벽에 닿아 야한다.
3Dave

11

C ++에서는 정적 클래스가 아닌 클래스의 정적 함수를 작성하려고합니다.

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

그런 다음 내가 원하는 것으로 추정되는 객체를 인스턴스화하지 않고 BitParser :: getBitAt ()를 사용하여 함수를 호출 할 수 있어야합니다.


11

같은 것을 쓸 수 있습니까 static class?

C ++ 11 N3337 표준 초안 부록 C 7.1.1 에 따르면 아니오 :

변경 : C ++에서 정적 또는 extern 지정자는 객체 또는 함수의 이름에만 적용 할 수 있습니다. C ++에서는 형식 지정과 함께 이러한 지정자를 사용하는 것은 불법입니다. C에서 이러한 지정자는 유형 선언에 사용될 때 무시됩니다. 예:

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

이론적 근거 : 스토리지 클래스 지정자는 유형과 연관 될 때 의미가 없습니다. C ++에서 클래스 멤버는 정적 스토리지 클래스 지정자로 선언 될 수 있습니다. 유형 선언에서 스토리지 클래스 지정자를 허용하면 코드가 혼동 될 수 있습니다.

그리고 같은 struct, class또한 형식 선언이다.

Annex A의 구문 트리를 걸어도 같은 내용을 추론 할 수 있습니다.

그것은 흥미 롭다 static structC 법적했지만, 영향을 미치지 아니합니다 : 왜 그리고 언제 C 프로그래밍 정적 구조를 사용하는 방법?


6

여기에서 언급했듯이 C ++에서이를 달성하는 더 좋은 방법은 네임 스페이스를 사용하는 것일 수 있습니다. 그러나 아무도 final키워드 를 언급하지 않았으므로 static classC ++ 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 ++에서는 생성자 / 소멸자를 private로 선언하여 얻을 수 있습니다. 최종 결과는 같습니다.


당신이 제안하는 것은 싱글 톤 클래스를 만들 수 있지만 정적 클래스와 동일하지 않습니다.
ksinkar

4

Managed C ++에서 정적 클래스 구문은 다음과 같습니다.

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

... 안하는 것보다 늦게하는 것이 낫다...


3

이것은 C ++에서 C ++에서 수행하는 방식과 유사합니다.

C # file.cs에서는 공용 함수 내에 private var를 가질 수 있습니다. 다른 파일에 있으면 다음과 같이 함수로 네임 스페이스를 호출하여 사용할 수 있습니다.

MyNamespace.Function(blah);

C ++에서 동일하게 적용하는 방법은 다음과 같습니다.

SharedModule.h

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

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

SharedModule.cpp

//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

3

다른 관리되는 프로그래밍 언어와 달리 "정적 클래스"는 C ++에서 의미가 없습니다. 정적 멤버 기능을 사용할 수 있습니다.


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 ++에서 "인스턴스화 할 수없는 클래스"동작을 달성하는 방법은 다음과 같습니다. private액세스 수정자를 사용하여 더미 순수 가상 함수를 선언하십시오 .

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.