2009-10-20 68 views
10

Tôi muốn biết một cú pháp tốt cho C++ getters và setters.Getter và setter, con trỏ hoặc tài liệu tham khảo, và cú pháp tốt để sử dụng trong c + +?

private: 
YourClass *pMember; 

setter là dễ dàng Tôi đoán:

void Member(YourClass *value){ 
    this->pMember = value; // forget about deleting etc 
} 

và getter? Tôi có nên sử dụng tài liệu tham khảo hoặc con trỏ const không?

dụ:

YourClass &Member(){ 
    return *this->pMember; 
} 

hoặc

YourClass *Member() const{ 
    return this->member; 
} 

whats sự khác biệt giữa chúng?

Cảm ơn,

Joe

EDIT:

xin lỗi, tôi sẽ sửa câu hỏi của tôi ... Tôi biết về tài liệu tham khảo và gợi ý, tôi đã hỏi về tài liệu tham khảo và gợi ý const, như thu khí, những gì sẽ là sự khác biệt giữa chúng trong mã của tôi, giống như trong tương lai, những gì tôi mong đợi sẽ mất nếu tôi đi theo cách này hay cách khác ...

vì vậy tôi đoán tôi sẽ sử dụng con trỏ const thay vì tham chiếu

Con trỏ const không thể xóa hoặc được đặt, phải không?

+0

bạn có thể muốn chỉnh sửa ví dụ của bạn để phản ánh chỉnh sửa của bạn. Ngay bây giờ bạn đang quay trở lại một con trỏ không const. –

+0

nhưng sau đó bản thân tôi sẽ không thể xóa bộ nhớ? Tôi cần phải biết làm thế nào để sở hữu một cách chính xác các biến và để cho những người khác gọi các getters mà không sửa đổi biến số dưới ... – Jonathan

+0

getters và setters là rất Java/C# style (không phải C++). Chúng là kết quả của những ngôn ngữ này dựa vào sự phản ánh và các khuôn khổ để xây dựng đối tượng. Getters và Setter phá hủy bản chất OO của một ngôn ngữ và cho phép bạn can thiệp vào cấu trúc bên trong của một đối tượng. Đây là apon cau mày trong C++ là nó phổ biến hơn để có đối tượng được tạo thành hoàn chỉnh sau khi hàm tạo hoàn tất. –

Trả lời

15

Là một luật chung:

  • Nếu NULL là một tham số hợp lệ hoặc giá trị trả về, sử dụng con trỏ.
  • Nếu NULL là NOT thông số hợp lệ hoặc giá trị trả về, hãy sử dụng tham chiếu.

Vì vậy, nếu setter có thể được gọi với NULL, hãy sử dụng con trỏ làm tham số. Nếu không, hãy sử dụng một tham chiếu.

Nếu nó hợp lệ để gọi getter của một đối tượng có chứa một con trỏ NULL, nó sẽ trả về một con trỏ. Nếu trường hợp như vậy là một bất biến bất hợp pháp, giá trị trả về phải là một tham chiếu. Sau đó getter sẽ ném một ngoại lệ, nếu biến thành viên là NULL.

+0

làm thế nào về: nếu null không phải là một tham số hợp lệ hoặc giá trị trả về, sử dụng trả về theo giá trị? (không có con trỏ, không có refs) – hasen

+0

Đó là cách đơn giản hơn. Nhưng sau đó một lần nữa câu hỏi là cách để tổng quát để trả lời một cách chính xác. –

+0

Tôi sẽ chỉ sử dụng trả lại theo giá trị để tạo sẵn, cho phần còn lại tôi muốn tránh chi phí sao chép-xây dựng. // cho getters, tất nhiên nếu bạn cần tính toán gì đó, thì cách khác –

2

sự khác nhau giữa chúng là gì?

Tham chiếu là bí danh của sự kiện (nó điều *). Một con trỏ là địa chỉ của sự vật. Nếu có một cơ hội mà những gì được chỉ định sẽ không có ở đó, thì có thể bạn không muốn trả lại tài liệu tham khảo. Tài liệu tham khảo nói với người gọi "Tôi sẽ cung cấp cho bạn một bí danh sẽ tồn tại khi tôi trả lại cho bạn". Trên thực tế, không có cách nào để kiểm tra tham chiếu để xem liệu nội dung cơ bản có hợp lệ hay không.

Với con trỏ, ngữ nghĩa, bạn ngụ ý rằng người gọi có thể muốn kiểm tra xem Thành viên có tồn tại hay không trước khi sử dụng nó. Điều này thường được thực hiện với một kiểm tra NULL.

Cuối cùng không có câu trả lời "đúng". Nó phụ thuộc vào hợp đồng của lớp học và nếu người gọi sẽ/nên/muốn kiểm tra xem "Thành viên" vẫn còn xung quanh.

Câu trả lời ngắn là con trỏ cho những thứ có thể được chỉ định ở nơi khác và tham chiếu cho bí danh "không được xếp chồng".

+0

xin lỗi, tôi sẽ sửa câu hỏi của mình ... Tôi biết về tài liệu tham khảo và con trỏ, tôi đã hỏi về tham chiếu và con trỏ const, như getters, cái gì sẽ là sự khác biệt giữa chúng trong mã của tôi, giống như trong tương lai, những gì tôi mong đợi sẽ mất nếu tôi đi theo cách này hay cách khác ... – Jonathan

7

Điều tốt nhất là cung cấp giao diện OO thực cho khách hàng ẩn chi tiết triển khai thực hiện. Getters and Setters không phải là OO.

+0

Tôi, nhưng tôi thích getters và setters thay vì biến công cộng. Tôi hầu như không sử dụng các biến công cộng, chỉ khi cần, hoặc ít nhất là các biến được bảo vệ – Jonathan

+6

Tôi nghĩ DevFred gợi ý rằng bạn tránh cả hai biến số/các biến và các biến công cộng. Thay vào đó, hãy thử định nghĩa các phương thức hoạt động làm công việc cho bạn. – Boojum

+0

bạn có thể cho tôi một ví dụ không? Tôi đang tạo ra các lớp trừu tượng và bắt nguồn từ họ và hầu hết các getters, setters và chức năng của tôi là ảo ... Tôi đoán tôi đã không nhận được những gì ông có nghĩa là – Jonathan

1

+1 khi đặt câu hỏi về việc sử dụng người định cư và getters. Nếu bạn phải sử dụng chúng và có khả năng nulls xem xét việc sử dụng boost :: shared_ptr. Bằng cách này, quyền sở hữu được xử lý cho bạn.

+0

Tôi không muốn sử dụng tăng cường, như tôi đang học, tôi muốn tạo dự án này với các thư viện ít bên ngoài có thể ... – Jonathan

+0

'boost :: shared_ptr' bây giờ là' std :: shared_ptr' trong C++ hiện đại , do đó, sự phụ thuộc bên ngoài không còn là một cái cớ để không sử dụng con trỏ thông minh nữa. – ulidtko

4

Như những người khác đã nói, sử dụng con trỏ nếu null là một khả năng.

Trong hầu hết các trường hợp, tôi muốn sử dụng tham chiếu khi có thể. Cá nhân, trong mã của tôi, tôi muốn sử dụng sự khác biệt giữa con trỏ và tham chiếu đến quyền sở hữu tín hiệu. Tôi nghĩ về các cuộc gọi với các tham chiếu là "cho mượn" một đối tượng cho một hàm hoặc lớp khác. Lớp gốc đã qua hoặc trả lại tài liệu tham khảo vẫn còn sở hữu nó, và chịu trách nhiệm về việc tạo, bảo trì và dọn dẹp nó. Khi mã của tôi vượt qua một con trỏ không const, mặt khác, nó thường có nghĩa là có một số loại chuyển giao hoặc chia sẻ quyền sở hữu đang diễn ra, với tất cả các trách nhiệm đòi hỏi.

(Và có, tôi thường sử dụng con trỏ thông minh. Đó là giống như tài liệu tham khảo trong tâm trí tôi. Tôi đang nói về mã cấp thấp hơn so với ở đây.)

0

Jonathan, những gì bạn đang sử dụng trình biên dịch? Có một cơ hội tuyệt vời mà shared_ptr đã được gửi kèm với nó như là một phần của việc thực hiện TR1 của trình biên dịch.

6

Mã của bạn trông rất tuyệt như thể bạn đã quen với một ngôn ngữ khác - trong C++ sử dụng this->x (đối với một ví dụ) là tương đối bất thường. Khi tất cả các mã được viết tốt, do đó, sử dụng một accessor hoặc mutator.

Mặc dù tôi khá bất thường trong khía cạnh đặc biệt này, tôi sẽ lưu lại (nói một lần nữa) khi nói rằng mã máy khách để sử dụng trình truy cập hoặc trình biến đổi trực tiếp là một ý tưởng tồi. Nếu bạn thành thật có một tình huống mà nó có ý nghĩa đối với mã máy khách để thao tác một giá trị trong đối tượng của bạn, thì mã máy khách nên sử dụng phép gán thông thường để đọc và/hoặc ghi giá trị đó.

Khi/nếu bạn cần kiểm soát giá trị nào được chỉ định, quá tải toán tử cho phép bạn thực hiện điều khiển đó mà không buộc cú pháp get/set xấu xí trên mã máy khách. Cụ thể, những gì bạn muốn là một lớp proxy (hoặc lớp mẫu). Chỉ với một ví dụ, một trong những tình huống phổ biến nhất mà mọi người muốn có được/thiết lập chức năng là một cái gì đó giống như một số đó là nghĩa vụ phải được giới hạn trong một số phạm vi cụ thể. Các setXXX kiểm tra giá trị mới cho được trong phạm vi, và getXXX trả về giá trị.

Nếu bạn muốn điều đó, một (khá) mẫu đơn giản có thể thực hiện công việc nhiều sạch hơn:

template <class T, class less=std::less<T> > 
class bounded { 
    const T lower_, upper_; 
    T val_; 

    bool check(T const &value) { 
     return less()(value, lower_) || less()(upper_, value); 
    } 

    void assign(T const &value) { 
     if (check(value)) 
      throw std::domain_error("Out of Range"); 
     val_ = value; 
    } 

public: 
    bounded(T const &lower, T const &upper) 
     : lower_(lower), upper_(upper) {} 

    bounded(bounded const &init) 
     : lower_(init.lower), upper_(init.upper) 
    { 
     assign(init); 
    } 

    bounded &operator=(T const &v) { assign(v); return *this; } 

    operator T() const { return val_; } 

    friend std::istream &operator>>(std::istream &is, bounded &b) { 
     T temp; 
     is >> temp; 

     if (b.check(temp)) 
      is.setstate(std::ios::failbit); 
     else 
      b.val_ = temp; 
     return is; 
    } 
}; 

này cũng làm cho các mã gần gũi hơn với tự chủ tài liệu - ví dụ, khi bạn khai báo một đối tượng như: bounded<int>(1, 1024);, ngay lập tức rõ ràng rằng mục đích là một số nguyên trong phạm vi từ 1 đến 1024. Phần duy nhất ai đó có thể tìm mở cho câu hỏi là liệu 1 và/hoặc 1024 có được bao gồm trong phạm vi không.Điều này khác biệt đáng kể so với việc xác định một int trong lớp, và mong đợi tất cả mọi người nhìn vào lớp để nhận ra rằng họ phải sử dụng setXXX để thực thi một số giới hạn trên các giá trị có thể giao.

Khi bạn nhúng một trong các thứ này vào một lớp, bạn đặt biến đó thành biến công khai và phạm vi vẫn được thực thi. Trong mã máy khách, không có đối số thực trên cú pháp - bạn chỉ gán cho một biến công khai, giống như bất kỳ biến nào khác - với các chi tiết nhỏ cố gán một giá trị nằm ngoài phạm vi sẽ ném một ngoại lệ. Về mặt lý thuyết, lớp có lẽ nên lấy một tham số mẫu chính sách để xác định chính xác những gì nó làm trong trường hợp đó, nhưng tôi chưa bao giờ có một lý do thực sự để làm phiền với điều đó.

+2

xin lỗi, nhưng nhìn vào mã đó làm cho khó hơn rất nhiều (ít nhất là đối với tôi) để hiểu hơn một getter hoặc một setter. Nó không phải là dễ dàng cho tôi để nghĩ rằng việc tạo ra một lớp templated hoặc chỉ là một lớp học cho một quy tắc đơn giản là tốt hơn so với một chức năng thiết lập đơn giản. Hãy nghĩ về một thư viện lớn sẽ được sử dụng bởi các thư viện hoặc thực thi khác, tôi nghĩ rằng quá tải toán tử gán không phải là cách tiếp cận tốt nhất, Tất nhiên tôi chỉ là một newbie, nhưng từ những gì tôi đã đọc, chỉ bị quá tải khi cần thiết, như Quy tắc của cây mà Martin đã viết trong chủ đề khác của tôi – Jonathan

+2

Nếu tất cả những gì bạn từng viết là một bộ lấy và một bộ setter, bạn sẽ đúng - mã này sẽ phức tạp hơn. Tuy nhiên, khi bạn có thể thay thế hàng chục (có khả năng là hàng trăm) các getters và setters bằng một mẫu, nó đơn giản hóa mã một cách đáng kể. Bất kể điều đó, nó tập trung tất cả sự phức tạp. Một getter và setter yêu cầu * tất cả * mã để nhận thức được việc thực hiện và thêm phức tạp để xử lý nó. Điều này đặt tất cả sự phức tạp ở một nơi và đơn giản hóa tất cả các mã khác. –

1

Bên cạnh những câu trả lời khác, nếu bạn chọn tài liệu tham khảo cho các getter không viết nó như trong ví dụ của bạn:

YourClass &Member(){ 
    return *this->pMember; 
} 

getter của bạn thực sự cho phép thiết lập, như trong instance->Member() = YourClass(); và do đó bỏ qua setter của bạn. Điều này có thể không được phép nếu YourClass không thể sao chép được, nhưng vẫn còn một điều khác cần lưu ý. Một nhược điểm khác là getter không phải là const.

Thay vào đó, hãy viết getter của bạn như thế này:

const YourClass &Member() const { 
    return *this->pMember; 
} 
Các vấn đề liên quan