Làm thế nào để bạn tạo một lớp tĩnh trong C ++?


263

Làm thế nào để bạn tạo một lớp tĩnh trong C ++? Tôi sẽ có thể làm một cái gì đó như:

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

Giả sử tôi đã tạo ra BitParserlớp học. Điều gì sẽ là BitParsercái nhìn định nghĩa lớp như thế nào?


198
Vâng. Quay lại thời xưa, chúng ta chỉ gọi đó là "chức năng". Bạn trẻ ngày hôm nay với "không gian tên" điên rồ của bạn. Này, ra khỏi bãi cỏ của tôi! <lắc nắm tay>
Vagrant

7
@Vagrant một chức năng trong một không gian tên vẫn là một chức năng. Một hàm thuộc về một lớp được gọi là một phương thức. Nếu nó là một phương thức tĩnh, bạn gọi nó tương tự như thể nó là một hàm bên trong một không gian tên.

48
5 năm sau và bây giờ tôi đứng về phía @Vagrant. Một câu hỏi thật ngớ ngẩn!
andrewrk

16
9 năm sau và bây giờ tôi có ngôn ngữ lập trình của riêng mình mà thậm chí không có lớp: ziglang.org
andrewrk

1
Các lớp giống như container IMO (chỉ có các phương thức tĩnh) rất hữu ích trong một số trường hợp nhất định.
AarCee

Câu trả lời:


272

Nếu bạn đang tìm cách áp dụng từ khóa "tĩnh" cho một lớp, chẳng hạn như bạn có thể trong C #, thì bạn sẽ không thể sử dụng mà không cần sử dụng Managed C ++.

Nhưng ngoại hình của mẫu của bạn, bạn chỉ cần tạo một phương thức tĩnh công khai trên đối tượng BitParser của bạn. Thích như vậy:

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;
}

Bạn có thể sử dụng mã này để gọi phương thức giống như mã ví dụ của bạn.

Mong rằng sẽ giúp! Chúc mừng.


2
OJ, bạn có một lỗi cú pháp . Từ khóa tĩnh chỉ nên được sử dụng trong định nghĩa lớp chứ không phải trong định nghĩa phương thức.
andrewrk

90
Để làm rõ ý định của bạn trong phương pháp này, bạn có thể sử dụng thêm một hàm tạo riêng. private: BitParser() {}Điều này sẽ ngăn bất cứ ai tạo ra các trường hợp.
Danvil

7
An toàn chủ đề @MoatazElmasry là một vấn đề khi bạn chia sẻ trạng thái. Trong triển khai trên, không có trạng thái được chia sẻ, vì vậy không thể có bất kỳ vấn đề nào về an toàn luồng ... trừ khi bạn đủ ngu ngốc để sử dụng các số liệu thống kê bên trong các chức năng đó. Vì vậy, có, mã ở trên là luồng an toàn, chỉ cần giữ trạng thái liên tục ra khỏi chức năng của bạn và bạn đang tốt.
OJ.

@MoatazElmasry Không chính xác. Hai luồng không thể sửa đổi các biến cục bộ không tĩnh trong hàm tĩnh.
OJ.

12
Nếu C ++ 11, tôi cho rằng tốt hơn hết BitParser() = delete;là truyền đạt đúng ý định loại bỏ hàm tạo (không chỉ ẩn nó như private).
phượng hoàng

247

Hãy xem xét giải pháp của Matt Price .

  1. Trong C ++, một "lớp tĩnh" không có nghĩa. Thứ gần nhất là một lớp chỉ có các phương thức tĩnh và các thành viên.
  2. Sử dụng phương pháp tĩnh sẽ chỉ giới hạn bạn.

Những gì bạn muốn là, được thể hiện trong ngữ nghĩa C ++, để đặt hàm của bạn (vì nó một hàm) trong một không gian tên.

Chỉnh sửa 2011-11-11

Không có "lớp tĩnh" trong C ++. Khái niệm gần nhất sẽ là một lớp chỉ có các phương thức tĩnh. Ví dụ:

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

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

Nhưng bạn phải nhớ rằng "các lớp tĩnh" là các hack trong loại ngôn ngữ giống như Java (ví dụ C #) không thể có các hàm không phải là thành viên, vì vậy chúng phải di chuyển chúng vào bên trong các lớp như các phương thức tĩnh.

Trong C ++, điều bạn thực sự muốn là một hàm không phải là thành viên mà bạn sẽ khai báo trong một không gian tên:

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

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

Tại sao vậy?

Trong C ++, không gian tên mạnh hơn các lớp cho mẫu "Phương thức tĩnh Java", bởi vì:

  • phương thức tĩnh có quyền truy cập vào các biểu tượng riêng
  • phương thức tĩnh riêng vẫn có thể nhìn thấy (nếu không thể truy cập được) đối với mọi người, điều này vi phạm phần nào sự đóng gói
  • phương thức tĩnh không thể được khai báo
  • Các phương thức tĩnh không thể bị quá tải bởi người dùng lớp mà không sửa đổi tiêu đề thư viện
  • không có gì có thể được thực hiện bằng một phương thức tĩnh không thể được thực hiện tốt hơn một hàm không phải là thành viên (có thể là bạn) trong cùng một không gian tên
  • không gian tên có ngữ nghĩa riêng của chúng (chúng có thể được kết hợp, chúng có thể ẩn danh, v.v.)
  • Vân vân.

Kết luận: Không sao chép / dán mẫu của Java / C # trong C ++. Trong Java / C #, mẫu là bắt buộc. Nhưng trong C ++, đó là phong cách xấu.

Chỉnh sửa 2010-06-10

Có một đối số có lợi cho phương thức tĩnh bởi vì đôi khi, người ta cần sử dụng một biến thành viên riêng tĩnh.

Tôi không đồng ý một chút, như hiển thị dưới đây:

Giải pháp "Thành viên riêng tĩnh"

// HPP

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

Đầu tiên, myGlobal được gọi là myGlobal vì nó vẫn là biến riêng tư toàn cầu. Nhìn vào nguồn CPP sẽ làm rõ rằng:

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

Ngay từ cái nhìn đầu tiên, thực tế là thanh chức năng miễn phíC không thể truy cập Foo :: myGlobal có vẻ là một điều tốt từ quan điểm đóng gói ... Thật tuyệt vì ai đó nhìn vào HPP sẽ không thể (trừ khi dùng đến phá hoại) để truy cập Foo :: myGlobal.

Nhưng nếu bạn nhìn kỹ, bạn sẽ thấy đó là một sai lầm khổng lồ: Không chỉ biến riêng tư của bạn vẫn phải được khai báo trong HPP (và do đó, hiển thị với tất cả mọi người, mặc dù là riêng tư), nhưng bạn phải khai báo trong cùng một chức năng HPP, tất cả (như trong TẤT CẢ) sẽ được phép truy cập vào nó !!!

Vì vậy, sử dụng một thành viên tĩnh riêng giống như đi ra ngoài trong hình khỏa thân với danh sách những người yêu bạn xăm trên da của bạn: Không ai được phép chạm vào, nhưng mọi người đều có thể nhìn trộm. Và phần thưởng: Mọi người đều có thể có tên của những người được ủy quyền để chơi với những người bạn của bạn.

private thực sự ... :-D

Giải pháp "Không gian tên ẩn danh"

Không gian tên ẩn danh sẽ có lợi thế làm cho mọi thứ riêng tư thực sự riêng tư.

Đầu tiên, tiêu đề HPP

// HPP

namespace Foo
{
   void barA() ;
}

Chỉ cần chắc chắn rằng bạn nhận xét: Không có tuyên bố vô dụng của barB cũng như myGlobal. Điều đó có nghĩa là không ai đọc tiêu đề biết những gì ẩn sau barA.

Sau đó, 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 !!!
}

Như bạn có thể thấy, giống như khai báo "lớp tĩnh", fooA và fooB vẫn có thể truy cập myGlobal. Nhưng không ai khác có thể. Và không ai khác ngoài CPP này biết fooB và myGlobal thậm chí còn tồn tại!

Không giống như "lớp tĩnh" đi trên khỏa thân với sổ địa chỉ của cô được xăm trên da, không gian tên "ẩn danh" được trang bị đầy đủ , có vẻ như AFAIK được gói gọn hơn.

Thật sự nó có ảnh hưởng sao?

Trừ khi những người sử dụng mã của bạn là phá hoại (Tôi sẽ cho bạn, như một bài tập, tìm cách người ta có thể truy cập vào các phần private của một lớp công chúng sử dụng một bẩn hành vi không xác định Hack ...), có chuyện gì privateprivate, ngay cả khi nó hiển thị trong privatephần của một lớp được khai báo trong một tiêu đề.

Tuy nhiên, nếu bạn cần thêm một "chức năng riêng tư" khác có quyền truy cập vào thành viên riêng tư, bạn vẫn phải khai báo nó với tất cả mọi người bằng cách sửa đổi tiêu đề, đó là một nghịch lý theo như tôi nghĩ: Nếu tôi thay đổi việc thực hiện mã của tôi (phần CPP), sau đó giao diện (phần HPP) KHÔNG nên thay đổi. Trích dẫn Leonidas: " Đây là ENCAPSULATION! "

Chỉnh sửa 2014-09-20

Khi nào các phương thức tĩnh thực sự tốt hơn các không gian tên với các hàm không phải là thành viên?

Khi bạn cần nhóm các chức năng lại với nhau và đưa nhóm đó vào một mẫu:

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

Bởi vì, nếu một lớp có thể là một tham số mẫu, thì một không gian tên không thể.


3
GCC hỗ trợ -fno-access-control, có thể được sử dụng trong các bài kiểm tra đơn vị whitebox để truy cập các thành viên lớp riêng khác. Đó là lý do duy nhất tôi có thể nghĩ ra để biện minh cho việc sử dụng một thành viên lớp thay vì toàn cầu ẩn danh / tĩnh trong việc triển khai.
Tom

8
@Tom: Một giải pháp đa nền tảng sẽ là thêm đoạn mã sau #define private publicvào tiêu đề ... ^ _ ^ ...
paercebal

1
@Tom: dù sao, IMHO, thậm chí xem xét thử nghiệm đơn vị, nhược điểm của "hiển thị quá nhiều thứ" vượt trội so với ưu điểm. Tôi đoán một giải pháp thay thế sẽ là đặt mã được kiểm tra trong một hàm lấy các tham số cần thiết (và không còn nữa) trong một utilitieskhông gian tên. Bằng cách này, chức năng này có thể được kiểm tra đơn vị và vẫn không có quyền truy cập đặc biệt vào các thành viên tư nhân (vì chúng được cung cấp dưới dạng tham số trong lệnh gọi hàm) ...
paercebal

@paercebal Tôi sắp nhảy lên tàu của bạn, nhưng tôi có một đặt phòng cuối cùng. Nếu ai đó nhảy vào ý chí của bạn, namespacehọ sẽ không có quyền truy cập vào global, mặc dù các thành viên bị ẩn? Rõ ràng là họ sẽ phải đoán, nhưng trừ khi bạn cố tình làm xáo trộn mã của bạn, các tên biến khá dễ đoán.
Zak

@Zak: Thật vậy, họ có thể, nhưng chỉ bằng cách cố gắng thực hiện nó trong tệp CPP nơi biến myGlobal được khai báo. Điểm này là khả năng hiển thị nhiều hơn khả năng tiếp cận. Trong lớp tĩnh, biến myGlobal là riêng tư, nhưng vẫn hiển thị. Điều này không quan trọng như vẻ ngoài của nó, nhưng trong một DLL, việc hiển thị một biểu tượng nên riêng tư với DLL trong tiêu đề được xuất có thể gây khó xử ... Trong không gian tên, myGlobal chỉ tồn tại trong tệp CPP (bạn thậm chí có thể đi xa hơn và làm cho nó tĩnh). Biến đó không xuất hiện trong các tiêu đề công khai.
paercebal

63

Bạn cũng có thể tạo một hàm miễn phí trong một không gian tên:

Trong BitParser.h

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

Trong BitParser.cpp

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

Nói chung đây sẽ là cách ưa thích để viết mã. Khi không có nhu cầu cho một đối tượng, đừng sử dụng một lớp.


1
Trong một số trường hợp, bạn có thể muốn đóng gói dữ liệu ngay cả khi lớp chủ yếu là "tĩnh". Các thành viên lớp tư nhân tĩnh sẽ cung cấp cho bạn điều này. Các thành viên không gian tên luôn công khai và không thể cung cấp dữ liệu đóng gói.
Torleif

Nếu var "thành viên" chỉ được khai báo và truy cập từ tệp .cpp, thì nó riêng tư hơn var riêng được khai báo trong tệp .h. KHÔNG phải là tôi khuyên bạn nên kỹ thuật này.
jmucchiello

3
@Torleif: Bạn đã sai. không gian tên tốt hơn cho đóng gói hơn các thành viên tư nhân tĩnh. Xem câu trả lời của tôi để trình diễn.
paercebal

1
có nhưng trong không gian tên, bạn phải giữ trật tự hàm, ngược lại với lớp có thành viên tĩnh, ví dụ void a () {b ();} b () {} sẽ gây ra lỗi trong không gian tên nhưng không phải trong lớp với thành viên tĩnh
Moataz Elmasry

13

Nếu bạn đang tìm cách áp dụng từ khóa "tĩnh" cho một lớp, chẳng hạn như bạn có thể trong C #

các lớp tĩnh chỉ là trình biên dịch giữ bạn và ngăn bạn viết bất kỳ phương thức / biến thể hiện nào.

Nếu bạn chỉ viết một lớp bình thường mà không có bất kỳ phương thức / biến thể hiện nào, thì đó là điều tương tự và đây là những gì bạn sẽ làm trong C ++


Không phàn nàn (đặc biệt là tại bạn), nhưng một số trình biên dịch cầm tay để giữ tôi không viết hoặc cắt / dán từ static200 lần sẽ là một điều tốt.
3Dave

Đồng ý - nhưng một lớp tĩnh trong C # cũng không làm điều này. Nó chỉ không biên dịch khi bạn quên dán tĩnh vào đó :-)
Orion Edwards

Vâng - đủ công bằng. Macro của tôi đang hiển thị. Thành thật mà nói, nếu tôi khai báo lớp là tĩnh, trình biên dịch chỉ nên đưa ra lỗi nếu tôi cố gắng khởi tạo nó. Các quy tắc đòi hỏi tôi phải lặp lại chính mình là đáng ghét và nên là người đầu tiên chống lại bức tường khi cuộc cách mạng đến.
3Dave

11

Trong C ++, bạn muốn tạo một hàm tĩnh của một lớp (không phải là một lớp tĩnh).

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

Sau đó, bạn có thể gọi hàm bằng BitParser :: getBitAt () mà không cần khởi tạo một đối tượng mà tôi cho là kết quả mong muốn.


11

Tôi có thể viết một cái gì đó như thế static classnào?

Không , theo dự thảo tiêu chuẩn C ++ 11 N3337 Phụ lục C 7.1.1:

Thay đổi: Trong C ++, chỉ định tĩnh hoặc chỉ định bên ngoài chỉ có thể được áp dụng cho tên của các đối tượng hoặc hàm. Sử dụng các specifier này với khai báo kiểu là bất hợp pháp trong C ++. Trong C, các chỉ định này được bỏ qua khi được sử dụng trên các khai báo kiểu. Thí dụ:

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

Đặt vấn đề: Trình xác định lớp lưu trữ không có bất kỳ ý nghĩa nào khi được liên kết với một loại. Trong C ++, các thành viên lớp có thể được khai báo bằng trình xác định lớp lưu trữ tĩnh. Việc cho phép các trình xác định lớp lưu trữ trên các khai báo kiểu có thể khiến mã bị nhầm lẫn cho người dùng.

Và giống như struct, classcũng là một loại khai báo.

Điều tương tự có thể được suy luận bằng cách đi bộ cây cú pháp trong Phụ lục A.

Thật thú vị khi lưu ý rằng đó static structlà hợp pháp trong C, nhưng không có tác dụng: Tại sao và khi nào nên sử dụng cấu trúc tĩnh trong lập trình C?


6

Như đã lưu ý ở đây, một cách tốt hơn để đạt được điều này trong C ++ có thể là sử dụng các không gian tên. Nhưng vì không ai đề cập đến finaltừ khóa ở đây, nên tôi đang đăng những gì tương đương trực tiếp static classtừ C # sẽ như thế nào trong C ++ 11 trở lên:

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

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

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

5

Bạn 'có thể có một lớp tĩnh trong C ++, như đã đề cập trước đó, một lớp tĩnh là một lớp không có bất kỳ đối tượng nào của nó khởi tạo nó. Trong C ++, điều này có thể thu được bằng cách khai báo hàm tạo / hàm hủy là riêng tư. Kết quả cuối cùng là như nhau.


Những gì bạn đang đề xuất có thể tạo ra một lớp đơn, nhưng nó không giống như một lớp tĩnh.
ksinkar

4

Trong Managed C ++, cú pháp lớp tĩnh là: -

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

... muộn còn hơn không...


3

Điều này tương tự như cách làm của C # trong C ++

Trong C # file.cs bạn có thể có var riêng bên trong một hàm công khai. Khi ở một tệp khác, bạn có thể sử dụng nó bằng cách gọi không gian tên với hàm như trong:

MyNamespace.Function(blah);

Đây là cách áp dụng tương tự trong 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
Nhưng mọi người cango vào TheDataToBeHidden -> Đó không phải là một giải pháp
Guy L

3

Không giống như ngôn ngữ lập trình được quản lý khác, "lớp tĩnh" KHÔNG có nghĩa trong C ++. Bạn có thể sử dụng chức năng thành viên tĩnh.


0

Một trường hợp trong đó các không gian tên có thể không hữu ích cho việc đạt được "các lớp tĩnh" là khi sử dụng các lớp này để đạt được thành phần trên sự kế thừa. Không gian tên không thể là bạn của các lớp và vì vậy không thể truy cập các thành viên riêng của một lớp.

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

Một (trong số rất nhiều) thay thế, nhưng thanh lịch nhất (theo ý kiến ​​của tôi) (so với việc sử dụng không gian tên và các hàm tạo riêng để mô phỏng hành vi tĩnh), cách để đạt được hành vi "lớp không thể khởi tạo" trong C ++ sẽ là khai báo một hàm ảo thuần với bộ privatesửa đổi truy cập.

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

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

Nếu bạn đang sử dụng C ++ 11, bạn có thể đi xa hơn để đảm bảo rằng lớp không được kế thừa (để mô phỏng hoàn toàn hành vi của một lớp tĩnh) bằng cách sử dụng trình finalxác định trong khai báo lớp để hạn chế các lớp khác kế thừa nó .

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

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

Nghe có vẻ ngớ ngẩn và phi logic, C ++ 11 cho phép khai báo một "hàm ảo thuần túy không thể bị ghi đè", mà bạn có thể sử dụng cùng với việc khai báo lớp finalđể thực hiện hoàn toàn và đầy đủ hành vi tĩnh do kết quả này dẫn đến kết quả lớp để không được kế thừa và hàm giả để không bị ghi đè dưới bất kỳ hình thức nào.

// 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.