Có phải đó là một ý tưởng tồi đã tạo ra một phương thức lớp được truyền các biến lớp?


14

Ý tôi là đây:

class MyClass {
    int arr1[100];
    int arr2[100];
    int len = 100;

    void add(int* x1, int* x2, int size) {
        for (int i = 0; i < size; i++) {
            x1[i] += x2[i];
        }
    }
};

int main() {
    MyClass myInstance;

    // Fill the arrays...

    myInstance.add(myInstance.arr1, myInstance.arr2, myInstance.len);
}

addđã có thể truy cập tất cả các biến mà nó cần, vì đó là một phương thức lớp, vậy đây có phải là một ý tưởng tồi không? Có những lý do tại sao tôi nên hoặc không nên làm điều này?


13
Nếu bạn cảm thấy thích làm điều này, đó là gợi ý về một thiết kế tồi. Các thuộc tính của lớp có lẽ thuộc về một nơi khác.
bitoflogic

11
Đoạn trích quá nhỏ. Mức độ trừu tượng hỗn hợp này không xảy ra trong các đoạn có kích thước này. Bạn cũng có thể khắc phục điều này với một số văn bản tốt về các cân nhắc thiết kế.
Joshua

16
Tôi thực sự không hiểu tại sao bạn thậm chí sẽ làm điều này. Bạn đạt được gì? Tại sao không chỉ có một addphương thức không tranh luận mà hoạt động trực tiếp trên nội bộ của nó? Chỉ là vì sao?
Ian Kemp

2
@IanKemp Hoặc có một addphương thức lấy các đối số nhưng không tồn tại như một phần của lớp. Chỉ là một hàm thuần túy để thêm hai mảng với nhau.
Kevin

Liệu phương thức add luôn thêm Array1 và Array2, hay nó có thể được sử dụng để thêm bất cứ thứ gì vào bất cứ thứ gì không?
dùng253751

Câu trả lời:


33

Có thể có những điều với lớp mà tôi sẽ làm khác đi, nhưng để trả lời câu hỏi trực tiếp, câu trả lời của tôi sẽ là

vâng, đó là một ý tưởng tồi

Lý do chính của tôi cho điều này là bạn không kiểm soát được những gì được truyền cho addhàm. Chắc chắn bạn hy vọng nó là một trong các mảng thành viên, nhưng điều gì xảy ra nếu ai đó đi qua một mảng khác có kích thước nhỏ hơn 100 hoặc bạn vượt qua trong một chiều dài lớn hơn 100?

Điều gì xảy ra là bạn đã tạo ra khả năng tràn bộ đệm. Và đó là một điều xấu xung quanh.

Để trả lời một số câu hỏi rõ ràng hơn (với tôi):

  1. Bạn đang trộn các mảng kiểu C với C ++. Tôi không phải là chuyên gia về C ++, nhưng tôi biết rằng C ++ có cách xử lý mảng tốt hơn (an toàn hơn)

  2. Nếu lớp đã có các biến thành viên, tại sao bạn cần phải chuyển chúng vào? Đây là nhiều câu hỏi kiến ​​trúc.

Những người khác có nhiều kinh nghiệm về C ++ hơn (tôi đã ngừng sử dụng nó 10 hoặc 15 năm trước) có thể có nhiều cách giải thích hùng hồn hơn về các vấn đề và có lẽ cũng sẽ đưa ra nhiều vấn đề hơn.


Thật vậy, vector<int>sẽ phù hợp hơn; chiều dài sau đó sẽ vô dụng. Ngoài ra const int len, vì mảng có chiều dài thay đổi không phải là một phần của tiêu chuẩn C ++ (mặc dù một số trình biên dịch hỗ trợ nó, bởi vì đó là một tính năng tùy chọn trong C).
Barshe

5
@Christophe Tác giả đã sử dụng mảng kích thước cố định, nên được thay thế bằng mảng std::arraykhông phân rã khi truyền nó cho các tham số, có cách thuận tiện hơn để lấy kích thước của nó, có thể sao chép và có quyền truy cập với kiểm tra giới hạn tùy chọn, trong khi không có chi phí nào Mảng kiểu C.
Khối

1
@Cubic: bất cứ khi nào tôi thấy mã khai báo mảng với kích thước cố định tùy ý (như 100), tôi khá chắc chắn tác giả là người mới bắt đầu không biết gì về ý nghĩa của điều vô nghĩa này và đó là một ý tưởng tốt để giới thiệu anh ấy / cô ấy thay đổi thiết kế này thành một cái gì đó với kích thước thay đổi.
Doc Brown

Các mảng là tốt.
Cuộc đua nhẹ nhàng với Monica

... Đối với những người không hiểu ý tôi, hãy xem Quy tắc Vô cực của Không ai
Doc Brown

48

Gọi một phương thức lớp với một số biến lớp không hẳn là xấu. Nhưng làm như vậy từ bên ngoài lớp học là một ý tưởng rất tồi và cho thấy một lỗ hổng cơ bản trong thiết kế OO của bạn, cụ thể là không có sự đóng gói thích hợp :

  • Bất kỳ mã nào sử dụng lớp của bạn sẽ cần phải biết đó lenlà độ dài của mảng và sử dụng nó một cách nhất quán. Điều này đi ngược lại với nguyên tắc ít kiến ​​thức nhất . Sự phụ thuộc như vậy vào các chi tiết bên trong của lớp rất dễ bị lỗi và rủi ro.
  • Điều này sẽ làm cho quá trình phát triển của lớp trở nên rất khó khăn (ví dụ: nếu một ngày nào đó, bạn muốn thay đổi các mảng kế thừa và lensang hiện đại hơn std::vector<int>), vì nó sẽ yêu cầu bạn thay đổi tất cả mã bằng cách sử dụng lớp của mình.
  • Bất kỳ phần nào của mã có thể tàn phá các MyClassđối tượng của bạn bằng cách làm hỏng một số biến công khai mà không tôn trọng các quy tắc (nên được gọi là bất biến lớp )
  • Cuối cùng, phương thức trong thực tế độc lập với lớp, vì nó chỉ hoạt động với các tham số và không phụ thuộc vào phần tử lớp nào khác. Loại phương thức này rất có thể là một hàm độc lập bên ngoài lớp. Hoặc ít nhất là một phương thức tĩnh.

Bạn nên cấu trúc lại mã của mình và:

  • biến các lớp của bạn privatehoặc protected, trừ khi có lý do chính đáng để không làm điều đó.
  • thiết kế các phương thức công khai của bạn như các hành động sẽ được thực hiện trên lớp (ví dụ object.action(additional parameters for the action):).
  • Nếu sau khi tái cấu trúc này, bạn vẫn có một số phương thức lớp cần được gọi với các biến lớp, làm cho chúng được bảo vệ hoặc riêng tư sau khi đã xác minh rằng chúng là các hàm tiện ích hỗ trợ các phương thức công khai.

7

Khi ý định của thiết kế này là bạn muốn có thể sử dụng lại phương thức này cho dữ liệu không xuất phát từ thể hiện của lớp này, thì bạn có thể muốn khai báo nó như một staticphương thức .

Một phương thức tĩnh không thuộc về bất kỳ thể hiện cụ thể nào của một lớp. Nó là một phương thức của lớp. Vì các phương thức tĩnh không được chạy trong ngữ cảnh của bất kỳ trường hợp cụ thể nào của lớp, nên chúng chỉ có thể truy cập các biến thành viên cũng được khai báo là tĩnh. Xem xét rằng phương thức của bạn addkhông đề cập đến bất kỳ biến thành viên nào được khai báo MyClass, đây là một ứng cử viên tốt để nhận được khai báo là tĩnh.

Tuy nhiên, các vấn đề an toàn được đề cập bởi các câu trả lời khác là hợp lệ: Bạn không kiểm tra xem cả hai mảng có lendài ít nhất không . Nếu bạn muốn viết C ++ hiện đại và mạnh mẽ, thì bạn nên tránh sử dụng mảng. Sử dụng lớp std::vectorthay vì bất cứ khi nào có thể. Trái với các mảng thông thường, vectơ biết kích thước riêng của chúng. Vì vậy, bạn không cần phải tự theo dõi kích thước của chúng. Hầu hết (không phải tất cả!) Các phương thức của họ cũng thực hiện kiểm tra ràng buộc tự động, điều này đảm bảo rằng bạn có được một ngoại lệ khi bạn đọc hoặc viết quá khứ. Truy cập mảng thông thường không thực hiện kiểm tra ràng buộc, điều này dẫn đến một segfault tốt nhất và có thể gây ra lỗ hổng tràn bộ đệm có thể khai thác ở mức tồi tệ hơn.


1

Một phương thức trong một lớp lý tưởng thao tác dữ liệu được đóng gói bên trong lớp. Trong ví dụ của bạn, không có lý do nào để phương thức add có bất kỳ tham số nào, chỉ cần sử dụng các thuộc tính của lớp.


0

Truyền (một phần) một đối tượng cho hàm thành viên là tốt. Khi thực hiện các chức năng của bạn, bạn luôn phải nhận thức được răng cưa có thể và hậu quả của chúng.

Tất nhiên, hợp đồng có thể để lại một số loại răng cưa không xác định ngay cả khi ngôn ngữ hỗ trợ nó.

Ví dụ nổi bật:

  • Tự giao. Điều này nên là một, có thể đắt tiền, không có op. Đừng bi quan trong trường hợp phổ biến cho nó.
    Mặt khác, việc tự di chuyển tự động, trong khi nó không nên nổ tung trên khuôn mặt của bạn, không cần phải là một kẻ không hoạt động.
  • Chèn một bản sao của một phần tử trong một thùng chứa vào thùng chứa. Một cái gì đó như v.push_back(v[2]);.
  • std::copy() giả định đích không bắt đầu bên trong nguồn.

Nhưng ví dụ của bạn có những vấn đề khác nhau:

  • Hàm thành viên không sử dụng bất kỳ đặc quyền nào của nó, cũng không tham chiếu this. Nó hoạt động như một chức năng tiện ích miễn phí, đơn giản là ở sai vị trí.
  • Lớp học của bạn không cố gắng trình bày bất kỳ loại trừu tượng nào . Cùng với đó, nó không cố gắng gói gọn các bộ phận bên trong của nó, cũng không duy trì bất kỳ sự bất biến nào . Đó là một túi ngu ngốc của các yếu tố tùy ý.

Tóm lại: Có, ví dụ này là xấu. Nhưng không phải vì hàm thành viên được thông qua các đối tượng thành viên.


0

Cụ thể làm điều này là một ý tưởng tồi vì một số lý do tốt, nhưng tôi không chắc tất cả chúng là không thể thiếu cho câu hỏi của bạn:

  • Có các biến lớp (biến thực tế, không phải hằng) là điều cần tránh nếu có thể, và sẽ kích hoạt một sự phản ánh nghiêm túc về việc bạn có thực sự cần nó hay không.
  • Để lại quyền truy cập ghi vào các biến lớp này hoàn toàn mở cho mọi người gần như là xấu.
  • Phương pháp lớp học của bạn cuối cùng không liên quan đến lớp học của bạn. Những gì nó thực sự là một chức năng thêm các giá trị của một mảng vào các giá trị của một mảng khác. Loại hàm này phải là một hàm trong ngôn ngữ có các hàm và phải là một phương thức tĩnh của "lớp giả" (nghĩa là một tập hợp các hàm không có dữ liệu hoặc hành vi đối tượng) trong các ngôn ngữ không có hàm.
  • Ngoài ra, loại bỏ các tham số này và biến nó thành một phương thức lớp thực, nhưng nó vẫn sẽ xấu vì những lý do được đề cập trước đó.
  • Tại sao int mảng? vector<int>sẽ làm cho cuộc sống của bạn dễ dàng hơn.
  • Nếu ví dụ này thực sự đại diện cho thiết kế của bạn, hãy xem xét đưa dữ liệu của bạn vào các đối tượng chứ không phải trong các lớp và cũng triển khai các hàm tạo cho các đối tượng này theo cách không yêu cầu thay đổi giá trị của dữ liệu đối tượng sau khi tạo.

0

Đây là cách tiếp cận "C với các lớp" cổ điển đối với C ++. Trong thực tế, đây không phải là điều mà bất kỳ lập trình viên C ++ dày dạn nào sẽ viết. Đối với một người, việc sử dụng mảng C thô là khá phổ biến, trừ khi bạn đang triển khai một thư viện container.

Một cái gì đó như thế này sẽ phù hợp hơn:

// Don't forget to compile with -std=c++17
#include <iostream>
using std::cout; // This style is less common, but I prefer it
using std::endl; // I'm sure it'll incite some strongly opinionated comments

#include <array>
using std::array;

#include <algorithm>

#include <vector>
using std::vector;


class MyClass {
public: // Begin lazy for brevity; don't do this
    std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
    std::array<int, 5> arr2 = {10, 10, 10, 10, 10};
};

void elementwise_add_assign(
    std::array<int, 5>& lhs,
    const std::array<int, 5>& rhs
) {
    std::transform(
        lhs.begin(), lhs.end(), rhs.begin(),
        lhs.begin(),
        [](auto& l, const auto& r) -> int {
            return l + r;
        });
}

int main() {
    MyClass foo{};

    elementwise_add_assign(foo.arr1, foo.arr2);

    for(auto const& value: foo.arr1) {
        cout << value << endl; // arr1 values have been added to
    }

    for(auto const& value: foo.arr2) {
        cout << value << endl; // arr2 values remain unaltered
    }
}
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.