Tham chiếu các biến thành viên với tư cách là thành viên lớp


82

Tại nơi làm việc của tôi, tôi thấy phong cách này được sử dụng rộng rãi: -

#include <iostream>

using namespace std;

class A
{
public:
   A(int& thing) : m_thing(thing) {}
   void printit() { cout << m_thing << endl; }

protected:
   const int& m_thing; //usually would be more complex object
};


int main(int argc, char* argv[])
{
   int myint = 5;
   A myA(myint);
   myA.printit();
   return 0;
}

Có tên nào để mô tả thành ngữ này không? Tôi giả sử nó là để ngăn chặn chi phí có thể lớn của việc sao chép một đối tượng phức tạp lớn?

Đây có phải là thực hành tốt không? Có bất kỳ cạm bẫy nào đối với cách tiếp cận này không?


4
Một cạm bẫy có thể xảy ra là nếu đối tượng mà bạn có tham chiếu đến trong biến thành viên bị phá hủy ở nơi khác và bạn cố gắng truy cập nó thông qua lớp học của mình
nhà toán học

Câu trả lời:


114

Có tên nào để mô tả thành ngữ này không?

Trong UML nó được gọi là tập hợp. Nó khác với thành phần ở chỗ đối tượng thành viên không thuộc sở hữu của lớp giới thiệu. Trong C ++, bạn có thể thực hiện tổng hợp theo hai cách khác nhau, thông qua các tham chiếu hoặc con trỏ.

Tôi giả sử nó là để ngăn chặn chi phí có thể lớn của việc sao chép một đối tượng phức tạp lớn?

Không, đó sẽ là một lý do thực sự tồi tệ để sử dụng cái này. Lý do chính cho sự kết hợp là đối tượng được chứa không thuộc sở hữu của đối tượng chứa và do đó vòng đời của chúng không bị ràng buộc. Đặc biệt, thời gian tồn tại của đối tượng được tham chiếu phải lâu hơn đối tượng được giới thiệu. Nó có thể đã được tạo sớm hơn nhiều và có thể tồn tại đến cuối vòng đời của vùng chứa. Bên cạnh đó, trạng thái của đối tượng được tham chiếu không được kiểm soát bởi lớp, nhưng có thể thay đổi bên ngoài. Nếu tham chiếu không phải là tham chiếu const, thì lớp có thể thay đổi trạng thái của một đối tượng nằm bên ngoài nó.

Đây có phải là thực hành tốt không? Có bất kỳ cạm bẫy nào đối với cách tiếp cận này không?

Nó là một công cụ thiết kế. Trong một số trường hợp, đó sẽ là một ý tưởng hay, trong một số trường hợp thì không. Cạm bẫy phổ biến nhất là thời gian tồn tại của đối tượng giữ tham chiếu không bao giờ được vượt quá thời gian tồn tại của đối tượng được tham chiếu. Nếu đối tượng bao quanh sử dụng tham chiếu sau khi đối tượng được tham chiếu bị hủy, bạn sẽ có hành vi không xác định. Nói chung tốt hơn là nên chọn bố cục hơn là tổng hợp, nhưng nếu bạn cần, nó cũng là một công cụ tốt như bất kỳ công cụ nào khác.


7
"Không, đó sẽ là một lý do thực sự tồi tệ để sử dụng cái này". Bạn có thể vui lòng nói rõ hơn về điểm này? Những gì có thể được sử dụng thay thế để đạt được điều đó?
coincoin

@coincoin: Để đạt được những gì chính xác?
David Rodríguez - dribeas

3
to prevent the possibly large overhead of copying a big complex object?
coincoin

3
@underscore_d cảm ơn câu trả lời của bạn. Sau đó, điều gì sẽ xảy ra khi bạn không thể sử dụng một trong hai. Hãy tưởng tượng chúng ta muốn chia sẻ cùng một đối tượng bên trong các lớp khác nhau. Bạn kết thúc với một bản sao của đối tượng cho mỗi lớp nếu bạn chuyển đối tượng thành viên này theo giá trị? Vì vậy, giải pháp là sử dụng con trỏ thông minh hoặc tham chiếu để tránh sao chép. Không?
coincoin vào

2
@ plats1 đó là những gì tôi vừa viết Tôi biết điều đó. Quan điểm của tôi là bạn có thể sử dụng con trỏ thông minh hoặc tham chiếu.
coincoin

33

Nó được gọi là tiêm phụ thuộc thông qua chèn hàm khởi tạo : lớp Alấy phụ thuộc làm đối số cho hàm tạo của nó và lưu tham chiếu đến lớp phụ thuộc dưới dạng biến riêng.

Có một phần giới thiệu thú vị trên wikipedia .

Để có tính đúng đắn, tôi sẽ viết:

using T = int;

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  // ...

private:
   const T &m_thing;
};

nhưng một vấn đề với lớp này là nó chấp nhận các tham chiếu đến các đối tượng tạm thời:

T t;
A a1{t};    // this is ok, but...

A a2{T()};  // ... this is BAD.

Tốt hơn là thêm (yêu cầu ít nhất C ++ 11):

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  A(const T &&) = delete;  // prevents rvalue binding
  // ...

private:
  const T &m_thing;
};

Dù sao nếu bạn thay đổi hàm tạo:

class A
{
public:
  A(const T *thing) : m_thing(*thing) { assert(thing); }
  // ...

private:
   const T &m_thing;
};

nó được đảm bảo khá nhiều rằng bạn sẽ không có con trỏ đến một trang tạm thời .

Ngoài ra, vì hàm tạo nhận một con trỏ, nên rõ ràng hơn đối với người dùng Alà họ cần chú ý đến thời gian tồn tại của đối tượng mà họ truyền qua.


Các chủ đề có liên quan là:


20

Có tên nào để mô tả thành ngữ này không?

Không có tên cho cách sử dụng này, nó được gọi đơn giản là "Tham chiếu với tư cách thành viên lớp" .

Tôi giả sử nó là để ngăn chặn chi phí có thể lớn của việc sao chép một đối tượng phức tạp lớn?

Có và cả những trường hợp bạn muốn liên kết thời gian tồn tại của một đối tượng này với một đối tượng khác.

Đây có phải là thực hành tốt không? Có bất kỳ cạm bẫy nào đối với cách tiếp cận này không?

Phụ thuộc vào cách sử dụng của bạn. Sử dụng bất kỳ tính năng ngôn ngữ nào cũng giống như "chọn ngựa cho các khóa học" . Điều quan trọng cần lưu ý là mọi ( gần như tất cả ) tính năng ngôn ngữ đều tồn tại bởi vì nó hữu ích trong một số trường hợp.
Có một số điểm quan trọng cần lưu ý khi sử dụng tham chiếu với tư cách là thành viên lớp:

  • Bạn cần đảm bảo rằng đối tượng được giới thiệu được đảm bảo tồn tại cho đến khi đối tượng lớp của bạn tồn tại.
  • Bạn cần khởi tạo thành viên trong danh sách khởi tạo thành viên của phương thức khởi tạo. Bạn không thể có một khởi tạo lười biếng , có thể có trong trường hợp thành viên con trỏ.
  • Trình biên dịch sẽ không tạo bản sao gán operator=()và bạn sẽ phải tự mình cung cấp. Việc xác định nhà điều hành của bạn =sẽ thực hiện hành động gì trong trường hợp như vậy rất phức tạp . Vì vậy, về cơ bản lớp của bạn trở thành không thể gán .
  • Tài liệu tham khảo không thể được NULLhoặc được thực hiện để tham chiếu bất kỳ đối tượng nào khác. Nếu bạn cần đặt lại, thì không thể thực hiện với một tham chiếu như trong trường hợp con trỏ.

Đối với hầu hết các mục đích thực tế (trừ khi bạn thực sự lo lắng về việc sử dụng bộ nhớ cao do kích thước thành viên) chỉ cần có một cá thể thành viên, thay vì con trỏ hoặc thành viên tham chiếu là đủ. Điều này giúp bạn tiết kiệm rất nhiều lo lắng về các vấn đề khác mà các thành viên tham chiếu / con trỏ mang theo mặc dù phải sử dụng thêm bộ nhớ.

Nếu bạn phải sử dụng một con trỏ, hãy đảm bảo rằng bạn sử dụng một con trỏ thông minh thay vì một con trỏ thô. Điều đó sẽ làm cho cuộc sống của bạn dễ dàng hơn nhiều với các con trỏ.


" Tôi cho rằng nó là để ngăn chặn chi phí có thể lớn của việc sao chép một đối tượng phức tạp lớn? Có" - vui lòng giải thích tại sao bạn nghĩ rằng mẫu này có liên quan đến việc tránh sao chép, vì tôi không thể thấy bất kỳ điều gì. nếu cái không phải'idiom 'này bằng cách nào đó lưu các bản sao cho bất kỳ ai như một tác dụng phụ của mục đích thực sự của nó, thì thiết kế ban đầu của chúng đã có sai sót nghiêm trọng khi bắt đầu và chỉ cần thay thế nó tại chỗ bằng mẫu này không chắc sẽ làm được những gì họ mong đợi .
underscore_d

@underscore_d Giả sử một lớp cần một lượng không nhỏ dữ liệu const và có thể có nhiều trường hợp của lớp này cùng một lúc, việc mỗi cá thể có bản sao dữ liệu const đó có thể là lãng phí không thể chấp nhận được. Vì vậy, lưu một tham chiếu const đến một vị trí bên ngoài của dữ liệu đó có thể được chia sẻ sẽ tiết kiệm rất nhiều việc sao chép. shared_ptr không nhất thiết phải là một giải pháp vì xử lý dữ liệu không nhất thiết phải được cấp phát động.
Không quan trọng

@Unimportant Tôi không chắc phản đối của mình là gì vào năm 2016. Tôi sử dụng tài liệu tham khảo với tư cách là thành viên trong lớp mọi lúc. Có thể tôi lo ngại rằng chỉ cần thay thế quyền sở hữu theo giá trị bằng các tham chiếu sẽ dẫn đến các câu hỏi trọn đời và không nhất thiết phải là panacaea hay điều gì đó luôn có thể được thực hiện theo tỷ lệ 1: 1. Tôi không biết.
underscore_d

1

C ++ cung cấp một cơ chế tốt để quản lý thời gian tồn tại của một đối tượng thông qua các cấu trúc class / struct. Đây là một trong những tính năng tốt nhất của C ++ so với các ngôn ngữ khác.

Khi bạn có các biến thành viên được hiển thị thông qua ref hoặc con trỏ, nó vi phạm nguyên tắc đóng gói. Thành ngữ này cho phép người tiêu dùng của lớp thay đổi trạng thái của một đối tượng A mà không cần nó (A) biết hoặc kiểm soát nó. Nó cũng cho phép người tiêu dùng giữ một tham chiếu / con trỏ đến trạng thái bên trong của A, ngoài thời gian tồn tại của đối tượng A. Đây là thiết kế tồi. Thay vào đó, lớp có thể được cấu trúc lại để giữ một tham chiếu / con trỏ đến đối tượng được chia sẻ (không sở hữu nó) và chúng có thể được thiết lập bằng cách sử dụng hàm tạo (Ủy quyền các quy tắc thời gian sống). Lớp của đối tượng được chia sẻ có thể được thiết kế để hỗ trợ đa luồng / đồng thời tùy trường hợp.


2
nhưng mã trong OP không có tham chiếu đến bất kỳ biến thành viên nào. nó có một biến thành viên là một tham chiếu. vì vậy, những điểm tuyệt vời, nhưng dường như không liên quan.
underscore_d

-3

Tham chiếu thành viên thường được coi là xấu. Họ làm cho cuộc sống khó khăn so với những người chỉ điểm thành viên. Nhưng nó không phải là đặc biệt phi thường, cũng không phải là một thành ngữ hay điều gì đó được đặt tên đặc biệt. Nó chỉ là bí danh.


23
Bạn có thể cung cấp tài liệu tham khảo để hỗ trợ thường được coi là xấu ?
David Rodríguez - dribeas
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.