Các lớp tiện ích không có gì ngoài các thành viên tĩnh là một mô hình chống trong C ++?


39

Câu hỏi Tôi nên đặt các hàm không liên quan đến một lớp ở đâu đã gây ra một số tranh luận về việc liệu C ++ có hợp lý để kết hợp các hàm tiện ích trong một lớp hay chỉ tồn tại chúng dưới dạng các hàm miễn phí trong một không gian tên.

Tôi đến từ nền C # trong đó tùy chọn thứ hai không tồn tại và do đó, xu hướng sử dụng các lớp tĩnh trong mã C ++ nhỏ mà tôi viết. Tuy nhiên, câu trả lời được bình chọn cao nhất cho câu hỏi đó cũng như một số ý kiến ​​nói rằng các hàm miễn phí sẽ được ưu tiên hơn, thậm chí đề xuất các lớp tĩnh là một mô hình chống. Tại sao lại như vậy trong C ++? Ít nhất là trên bề mặt, các phương thức tĩnh trên một lớp dường như không thể phân biệt được với các hàm tự do trong một không gian tên. Tại sao lại ưu tiên cho cái sau?

Mọi thứ sẽ khác đi, nếu bộ sưu tập các chức năng tiện ích cần một số dữ liệu chia sẻ, ví dụ như bộ đệm có thể lưu trữ trong trường tĩnh riêng?


Nghe có vẻ giống như antipotype "phân rã chức năng".
user281377

7
Câu trả lời ngắn: Bạn không cần một lớp để bọc các chức năng này. Các chức năng miễn phí phù hợp với nhiệm vụ của bạn hơn là nhét chúng vào một số cấu trúc giả OO, đây là một cách giải quyết bạn chỉ cần trong các ngôn ngữ "thuần túy-OO".
Chris nói Phục hồi lại

Câu trả lời:


39

Tôi đoán để trả lời rằng chúng ta nên so sánh ý định của cả hai lớp và không gian tên. Theo Wikipedia:

Lớp học

Trong lập trình hướng đối tượng, một lớp là một cấu trúc được sử dụng như một kế hoạch chi tiết để tạo ra các thể hiện của chính nó - được gọi là các thể hiện của lớp, các đối tượng lớp, các đối tượng thể hiện hoặc các đối tượng đơn giản. Một lớp định nghĩa các thành viên cấu thành cho phép các thể hiện của lớp này có trạng thái và hành vi. Các thành viên trường dữ liệu (biến thành viên hoặc biến thể hiện) cho phép một đối tượng lớp duy trì trạng thái. Các loại thành viên khác, đặc biệt là các phương thức, cho phép hành vi của một đối tượng lớp. Các thể hiện của lớp là loại của lớp liên quan.

Không gian tên

Nói chung, một không gian tên là một thùng chứa cung cấp ngữ cảnh cho các định danh (tên, hoặc thuật ngữ kỹ thuật hoặc từ) mà nó giữ và cho phép phân định các từ định danh từ đồng nghĩa nằm trong các không gian tên khác nhau.

Bây giờ, bạn đang cố gắng đạt được điều gì bằng cách đặt các hàm trong một lớp (tĩnh) hoặc một không gian tên? Tôi muốn đặt ra rằng định nghĩa của một không gian tên mô tả rõ hơn ý định của bạn - tất cả những gì bạn muốn là một thùng chứa cho các chức năng của bạn. Bạn không cần bất kỳ tính năng nào được mô tả trong định nghĩa lớp. Lưu ý rằng các từ đầu tiên của định nghĩa lớp là " Trong lập trình hướng đối tượng ", tuy nhiên không có gì hướng đối tượng về một tập hợp các hàm.

Có lẽ cũng có những lý do kỹ thuật nhưng như một người nào đó đến từ Java và cố gắng tìm hiểu về ngôn ngữ đa mô hình là C ++, câu trả lời rõ ràng nhất với tôi là: Bởi vì chúng ta không cần OO để đạt được điều này.


1
+1 cho "chúng tôi không cần OO để đạt được điều này"
Ixrec

Tôi đồng ý với điều này là không có người hâm mộ OO, nhưng tôi đoán rằng có một vài tình huống trong đó sử dụng lớp All-static là giải pháp duy nhất, ví dụ, thay thế một không gian tên, vì bạn không thể khai báo một không gian tên lồng nhau bên trong một lớp học.
Wael Boutglay

26

Tôi sẽ rất thận trọng khi gọi đó là một mô hình chống. Không gian tên thường được ưa thích, nhưng vì không có mẫu không gian tên và không gian tên không thể được chuyển qua làm tham số mẫu, nên sử dụng các lớp không có gì ngoài các thành viên tĩnh là khá phổ biến.


3
Các câu trả lời khác đã phủ bóng lên dòng cuối cùng của OP. Không gian tên có phạm vi được đặt tên khá nhiều, vì vậy nếu bạn cần kiểm soát truy cập, các lớp là công cụ duy nhất có sẵn.
cmannett85

2
@ cbamber85 để công bằng, tôi đã đặt dòng đó chỉ sau khi đọc hai câu trả lời đầu tiên.
PersonalNexus

@PersonalNexus Ah đủ công bằng, tôi đã không nhận ra!
cmannett85

10
@ cbamber85: Điều đó không hoàn toàn đúng. Bạn thậm chí còn được đóng gói tốt hơn bằng cách đặt các hàm "riêng tư" trong tệp triển khai, vì vậy người dùng không được xem ngay cả khai báo riêng tư.
ChúBens

2
+1 Mẫu thực sự là một điểm mà không gian tên vẫn thiếu tính năng.
Chris nói Phục hồi lại

13

Ngày trước tôi cần học lớp FORTRAN. Lúc đó tôi đã biết các ngôn ngữ bắt buộc khác, vì vậy tôi nghĩ rằng tôi có thể học FORTRAN mà không cần học nhiều. Khi tôi làm bài tập về nhà đầu tiên, giáo sư đã trả lại cho tôi và yêu cầu làm lại: ông nói rằng các chương trình Pascal được viết theo cú pháp FORTRAN không được tính là bài nộp hợp lệ.

Vấn đề tương tự đang diễn ra ở đây: sử dụng các lớp tĩnh để lưu trữ các hàm tiện ích trong C ++ là một thành ngữ xa lạ với C ++.

Như bạn đã đề cập trong câu hỏi của mình, sử dụng các lớp tĩnh cho các hàm tiện ích trong C # là vấn đề cần thiết: các hàm đứng tự do đơn giản không phải là một tùy chọn trong C #. Ngôn ngữ cần thiết để phát triển một mẫu cho phép các lập trình viên xác định các hàm đứng tự do theo một cách khác - cụ thể là, trong các lớp tiện ích tĩnh. Đây là một mẹo Java được sử dụng từng từ: ví dụ: java.lang.MathSystem.Math của .NET gần như là đẳng cấu.

C ++, tuy nhiên, cung cấp các không gian tên, một phương tiện khác để đạt được cùng một mục tiêu và nó tích cực sử dụng nó trong việc thực hiện thư viện chuẩn của nó. Thêm một lớp bổ sung của các lớp tĩnh không chỉ không cần thiết mà còn hơi phản cảm đối với người đọc không có nền tảng C # hoặc Java. Theo một nghĩa nào đó, bạn đang giới thiệu một "bản dịch cho vay" sang ngôn ngữ cho một cái gì đó có thể được diễn đạt nguyên bản.

Khi các chức năng của bạn cần chia sẻ dữ liệu, tình hình sẽ khác. Vì các chức năng của bạn không còn không liên quan , mẫu Singleton trở thành cách ưa thích để giải quyết yêu cầu này.


6
+1, ngoại trừ đề xuất singletons. Chúng không được ưa thích trong C ++! Các hàm có thể nằm trong một lớp nếu chúng cần chia sẻ trạng thái, nhưng các lớp không nên là singletons trừ khi chúng thực sự, thực sự cần phải có. Và họ thường không.
Tối đa

@Maxpm Đừng hiểu sai ý tôi, singleton chỉ được ưu tiên cho các biến tĩnh toàn cầu. Ngoài ra, không có gì "ưa thích" về điều đó.
dasblinkenlight

2
Có bài viết hay này, Singletons: Giải quyết các vấn đề bạn không biết bạn chưa từng có từ năm 1995 . Tóm tắt nó nói rằng việc ghép ví dụ nổi tiếng với chính lớp đó sẽ hạn chế sự linh hoạt của bạn và không mang lại cho bạn điều gì. Hầu hết thời gian chỉ có một lớp học và có một ví dụ được chia sẻ ở đâu đó, nhưng đừng biến nó thành đơn lẻ.
Jan Hudec

Tôi không chắc chắn "thành ngữ nước ngoài" là thuật ngữ đúng. Đó là một thành ngữ rất phổ biến. Tôi nghĩ rằng khá nhiều nhóm lập trình đang sử dụng e2 của Stroustrup ...
Nick Keighley

10

Có lẽ bạn cần hỏi tại sao bạn muốn có một lớp toàn tĩnh?

Lý do duy nhất tôi có thể nghĩ đến là các ngôn ngữ khác (Java và C #) rất nhiều 'mọi thứ đều là một lớp' yêu cầu chúng. Các ngôn ngữ này hoàn toàn không thể tạo ra các hàm cấp cao nhất, vì vậy chúng đã phát minh ra một mẹo để giữ chúng và đó là hàm thành viên tĩnh. Chúng là một cách giải quyết, nhưng C ++ không cần một thứ như vậy, bạn có thể trực tiếp tạo ra các chức năng độc lập, cấp cao hoàn toàn mới.

Nếu bạn cần các hàm hoạt động trên một mục dữ liệu cụ thể, thì sẽ hợp lý khi bó chúng vào một lớp chứa dữ liệu, nhưng sau đó, chúng dừng lại là các hàm và bắt đầu là thành viên của lớp đó hoạt động trên dữ liệu của lớp.

Nếu bạn có một chức năng không hoạt động trên một loại dữ liệu cụ thể (tôi sử dụng từ ở đây vì các lớp là cách để xác định các loại dữ liệu mới) thì đó thực sự là một mô hình chống đẩy chúng vào một lớp, không phải là ngoài mục đích ngữ nghĩa.


10
Thật vậy - tôi sẽ nói rằng "lớp có tất cả các thành viên tĩnh" trong Java thực sự là một bản hack để vượt qua giới hạn của Java.
Charles Salvia

@CharlesSalvia: Tôi đã lịch sự :) Tôi cũng có cảm giác tồi tệ tương tự về việc main () là một phần của lớp java, mặc dù tôi hiểu tại sao họ làm vậy.
gbjbaanb

Điều này thực sự dường như cung cấp câu trả lời cho lý do tại sao bạn muốn một lớp toàn tĩnh. Hay tôi hiểu lầm bạn? Ví dụ, tôi cần giữ một biến có giá trị có thể thay đổi được đọc bởi một lớp (mà tôi dự định lưu trữ trong ROM) và được ghi bởi một lớp khác (sẽ nằm trong RAM). Đơn giản chỉ cần đặt tại một vị trí ram đã biết là không đủ - gói nó (và nó là phụ kiện) trong một lớp cho phép tôi điều chỉnh kiểm soát truy cập. Khá nhiều những gì bạn mô tả trong đoạn thứ hai của bạn.
iheanyi

5

Ít nhất là trên bề mặt, các phương thức tĩnh trên một lớp dường như không thể phân biệt được với các hàm tự do trong một không gian tên.

Nói cách khác, có một lớp thay vì một không gian tên không có lợi thế.

Tại sao lại ưu tiên cho cái sau?

Đối với một điều, nó giúp bạn tiết kiệm statictất cả thời gian, mặc dù đó được cho là một lợi ích khá nhỏ.

Lợi ích chính là nó là công cụ ít mạnh nhất cho công việc. Các lớp có thể được sử dụng để tạo các đối tượng, chúng có thể được sử dụng làm kiểu biến hoặc làm đối số khuôn mẫu. Cả hai đều là những tính năng bạn muốn cho bộ sưu tập các chức năng của bạn. Vì vậy, tốt hơn là sử dụng một công cụ không có các tính năng đó, để lớp không thể vô tình bị sử dụng sai.

Theo đó, việc sử dụng một không gian tên sẽ giúp mọi người sử dụng mã của bạn rõ ràng rằng đây là một tập hợp các hàm chứ không phải là một kế hoạch chi tiết để tạo các đối tượng.


5

Tuy nhiên, một số ý kiến ​​nói rằng các hàm miễn phí sẽ được ưu tiên, thậm chí đề xuất các lớp tĩnh là một kiểu chống. Tại sao lại như vậy trong C ++? Ít nhất là trên bề mặt, các phương thức tĩnh trên một lớp dường như không thể phân biệt được với các hàm tự do trong một không gian tên. Tại sao lại ưu tiên cho cái sau?

Một lớp hoàn toàn tĩnh sẽ hoàn thành công việc, nhưng nó giống như lái một chiếc xe tải bán hàng dài 53 feet đến cửa hàng tạp hóa để mua chip và salsa khi một chiếc xe bốn cửa sẽ làm (nghĩa là quá mức cần thiết). Các lớp học đi kèm với một lượng nhỏ chi phí bổ sung và sự tồn tại của chúng có thể mang lại cho ai đó ấn tượng rằng việc bắt đầu một người có thể là một ý tưởng tốt. Các hàm miễn phí được cung cấp bởi C ++ (và C, trong đó tất cả các hàm đều miễn phí) không làm điều đó; chúng chỉ là chức năng và không có gì khác.

Mọi thứ sẽ khác đi, nếu bộ sưu tập các chức năng tiện ích cần một số dữ liệu chia sẻ, ví dụ như bộ đệm có thể lưu trữ trong trường tĩnh riêng?

Không hẳn vậy. Mục đích ban đầu của staticC (và sau này là C ++) là cung cấp lưu trữ liên tục có thể sử dụng được ở mọi cấp độ phạm vi:

int counter() {
    static int value = 0;
    value++;
}

Bạn có thể đưa phạm vi ra tới mức của một tệp, làm cho nó hiển thị với tất cả các phạm vi hẹp hơn nhưng không nằm ngoài tệp:

static int value = 0;

int up() { value++; }
int down() { value--; }

Một thành viên lớp tĩnh riêng trong C ++ phục vụ cùng một mục đích nhưng bị giới hạn trong phạm vi của một lớp. Kỹ thuật được sử dụng trong counter()ví dụ trên cũng hoạt động bên trong các phương thức C ++ và là điều tôi thực sự khuyên bạn nên làm nếu biến không cần phải hiển thị cho toàn bộ lớp.


Tôi cho rằng một nhóm các hàm tiện ích không liên quan không chia sẻ dữ liệu nên được nhóm tốt hơn trong một không gian tên. Nhưng nếu bạn có một nhóm các hàm tiện ích được kết hợp chặt chẽ cần truy cập vào dữ liệu được chia sẻ và có lẽ một số điều khiển truy cập, một lớp tĩnh là lựa chọn tốt nhất. Một tĩnh trong một chức năng không được reentrant. Sử dụng một mô-đun toàn cầu ... tốt hơn một chút trong ngữ cảnh này, nhưng tôi vẫn nghĩ rằng một lớp tĩnh là giải pháp tốt nhất nếu bạn có các yêu cầu tôi mô tả.
iheanyi

Nếu họ chia sẻ dữ liệu, họ có thể tốt hơn khi là một lớp không có phương thức tĩnh?
Nick Keighley

@NickKeighley Điều đó phụ thuộc vào việc ai là người chiến thắng trong cuộc tranh luận về việc liệu có tốt hơn để tạo một cá thể và chuyển nó cho mọi thứ cần nó hay chỉ khiến nó trở thành tĩnh trong lớp.
Blrfl

1

Nếu một hàm duy trì không có trạng thái và được phát lại, thì dường như không có nhiều điểm trong việc đẩy nó vào trong một lớp, (trừ khi ngôn ngữ bị ép buộc). Nếu hàm duy trì một số trạng thái, (ví dụ: nó chỉ có thể được tạo ra an toàn luồng bằng phương thức tĩnh), thì các phương thức tĩnh trên một lớp có vẻ phù hợp.


1

Phần lớn các diễn ngôn về chủ đề ở đây có ý nghĩa, mặc dù có một cái gì đó rất cơ bản về C ++ làm cho namespacesclasses / structs rất khác nhau.

Các lớp tĩnh (các lớp trong đó tất cả các thành viên là tĩnh và lớp sẽ không bao giờ được khởi tạo) chính là các đối tượng. Chúng không chỉ đơn giản là namespaceđể chứa các chức năng.

Lập trình meta mẫu cho phép chúng ta sử dụng một lớp tĩnh làm đối tượng thời gian biên dịch.

Xem xét điều này:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

Để sử dụng nó, chúng ta cần các hàm có trong một lớp. Một không gian tên sẽ không hoạt động ở đây. Xem xét:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

Bây giờ để sử dụng nó:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

Miễn là một công cụ cấp phát mới cho thấy một chức năng allocatereleasetương thích, việc chuyển sang một công cụ cấp phát mới là dễ dàng.

Điều này không thể đạt được với không gian tên.

Bạn luôn cần các chức năng là một phần của lớp học? Không.

Là sử dụng một lớp tĩnh một mô hình chống? Nó phụ thuộc vào ngữ cảnh.

Mọi thứ sẽ khác đi, nếu bộ sưu tập các chức năng tiện ích cần một số dữ liệu chia sẻ, ví dụ như bộ đệm có thể lưu trữ trong trường tĩnh riêng?

Trong trường hợp đó, những gì bạn đang cố gắng đạt được có khả năng được phục vụ tốt nhất thông qua lập trình hướng đối tượng.


Việc sử dụng từ khóa nội tuyến là dư thừa ở đây vì các phương thức được định nghĩa trong một định nghĩa lớp là ngầm định nội tuyến. Xem en.cppreference.com/w/cpp/lingu/inline .
Trevor

1

Như John Carmack nổi tiếng đã nói:

"Đôi khi, việc thực hiện tao nhã chỉ là một hàm. Không phải là một phương thức. Không phải là một lớp. Không phải là một khung công tác. Chỉ là một hàm."

Tôi nghĩ rằng khá nhiều tiền nó lên. Tại sao bạn lại biến nó thành một lớp mạnh mẽ nếu nó rõ ràng không phải là một lớp? C ++ có sự sang trọng mà bạn thực sự có thể sử dụng các chức năng; trong Java, mọi thứ đều là một phương thức. Sau đó, bạn cần các lớp tiện ích, và rất nhiều trong số họ.

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.