Khi nào nên sử dụng tài liệu tham khảo so với con trỏ


381

Tôi hiểu cú pháp và ngữ nghĩa chung của con trỏ so với tham chiếu, nhưng tôi nên quyết định như thế nào khi sử dụng tham chiếu hoặc con trỏ trong API?

Đương nhiên, một số tình huống cần cái này hoặc cái kia ( operator++cần một đối số tham chiếu), nhưng nói chung tôi thấy tôi thích sử dụng con trỏ (và con trỏ const) vì cú pháp rõ ràng là các biến đang được truyền một cách triệt để.

Ví dụ: trong đoạn mã sau:

void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
  int a = 0;
  add_one(a); // Not clear that a may be modified
  add_one(&a); // 'a' is clearly being passed destructively
}

Với con trỏ, luôn luôn (rõ ràng hơn) những gì đang diễn ra, vì vậy đối với các API và tương tự như sự rõ ràng là mối quan tâm lớn là con trỏ không phù hợp hơn tham chiếu? Điều đó có nghĩa là tài liệu tham khảo chỉ nên được sử dụng khi cần thiết (ví dụ operator++)? Có bất kỳ mối quan tâm hiệu suất với một hoặc khác?

EDIT (NGOÀI):

Bên cạnh việc cho phép các giá trị NULL và xử lý các mảng thô, có vẻ như sự lựa chọn tùy thuộc vào sở thích cá nhân. Tôi đã chấp nhận câu trả lời bên dưới tham khảo Hướng dẫn về Phong cách C ++ của Google , vì họ đưa ra quan điểm rằng "Tài liệu tham khảo có thể gây nhầm lẫn, vì chúng có cú pháp giá trị nhưng ngữ nghĩa con trỏ.".

Do công việc bổ sung cần thiết để vệ sinh các đối số con trỏ không phải là NULL (ví dụ: add_one(0)sẽ gọi phiên bản con trỏ và ngắt trong thời gian chạy), nên sử dụng các tham chiếu trong đó một đối tượng PHẢI có mặt, mặc dù đó là một sự xấu hổ để mất sự rõ ràng cú pháp.


4
Có vẻ như bạn đã đưa ra quyết định của mình về việc nên sử dụng cái nào khi nào. Cá nhân, tôi thích truyền vào đối tượng mà tôi đang hành động, cho dù tôi có sửa đổi nó hay không. Nếu một hàm lấy một con trỏ, điều đó cho tôi biết rằng nó hoạt động trên các con trỏ, tức là sử dụng chúng làm các trình vòng lặp trong một mảng.
Benjamin Lindley

1
@Schnommus: Đủ công bằng, tôi chủ yếu sử dụng TextMate. Tuy nhiên, tôi nghĩ rằng tốt hơn là ý nghĩa sẽ rõ ràng từ một cái nhìn thoáng qua.
connec

4
Điều gì về add_one(a);không rõ ràng rằng asẽ được sửa đổi? Nó nói ngay trong mã: thêm một .
GManNickG

32
@connec: Hướng dẫn kiểu Google C ++ không được coi là hướng dẫn kiểu C ++ tốt. Đây là một hướng dẫn về phong cách để làm việc với cơ sở mã C ++ cũ của Google (nghĩa là tốt cho công cụ của họ). Chấp nhận một câu trả lời dựa trên điều đó không giúp được ai. Chỉ cần đọc ý kiến ​​và giải thích của bạn, bạn đã đến câu hỏi này với một ý kiến ​​đã được đặt ra và chỉ tìm kiếm người khác để xác nhận quan điểm của bạn. Kết quả là bạn đang căn cứ vào câu hỏi và trả lời cho những gì bạn muốn / mong đợi được nghe.
Martin York

1
Điều này chỉ đơn giản là cố định bằng cách đặt tên phương thức addOneTo(...). Nếu đó không phải là điều bạn muốn làm, chỉ cần nhìn vào tờ khai.
stefan

Câu trả lời:


296

Sử dụng tài liệu tham khảo bất cứ nơi nào bạn có thể, con trỏ bất cứ nơi nào bạn phải.

Tránh con trỏ cho đến khi bạn không thể.

Lý do là con trỏ làm cho mọi thứ khó theo dõi / đọc hơn, các thao tác kém an toàn và nguy hiểm hơn nhiều so với bất kỳ cấu trúc nào khác.

Vì vậy, quy tắc của ngón tay cái là chỉ sử dụng con trỏ nếu không có lựa chọn nào khác.

Ví dụ, trả về một con trỏ cho một đối tượng là một tùy chọn hợp lệ khi hàm có thể trả về nullptr trong một số trường hợp và nó được giả định là nó sẽ. Điều đó nói rằng, một lựa chọn tốt hơn sẽ là sử dụng một cái gì đó tương tự nhưboost::optional .

Một ví dụ khác là sử dụng các con trỏ tới bộ nhớ thô cho các thao tác bộ nhớ cụ thể. Điều đó nên được ẩn và cục bộ trong các phần rất hẹp của mã, để giúp hạn chế các phần nguy hiểm của toàn bộ cơ sở mã.

Trong ví dụ của bạn, không có điểm nào trong việc sử dụng một con trỏ làm đối số vì:

  1. nếu bạn cung cấp nullptrlàm đối số, bạn sẽ đi vào vùng đất không xác định hành vi;
  2. phiên bản thuộc tính tham chiếu không cho phép (không có thủ thuật dễ dàng phát hiện) vấn đề với 1.
  3. phiên bản thuộc tính tham chiếu đơn giản hơn để hiểu cho người dùng: bạn phải cung cấp một đối tượng hợp lệ, không phải là thứ có thể là null.

Nếu hành vi của hàm sẽ phải làm việc có hoặc không có một đối tượng nhất định, thì việc sử dụng một con trỏ làm thuộc tính cho thấy rằng bạn có thể chuyển nullptrlàm đối số và điều đó tốt cho hàm. Đó là một hợp đồng giữa người dùng và việc thực hiện.


49
Tôi không chắc chắn rằng con trỏ làm cho bất cứ điều gì khó đọc hơn? Đây là một khái niệm khá đơn giản và làm cho nó rõ ràng khi một cái gì đó có khả năng được sửa đổi. Nếu bất cứ điều gì tôi nói khó đọc hơn khi không có dấu hiệu gì về những gì đang xảy ra, tại sao add_one(a)không nên trả lại kết quả, thay vì đặt nó theo tham chiếu?
connec

46
@connec: Nếu add_one(a)khó hiểu thì đó là vì nó được đặt tên không đúng. add_one(&a)sẽ có cùng một sự nhầm lẫn, chỉ bây giờ bạn có thể tăng con trỏ chứ không phải đối tượng. add_one_inplace(a)sẽ tránh tất cả sự nhầm lẫn.
Nicol Bolas

20
Một điểm, tài liệu tham khảo có thể đề cập đến bộ nhớ có thể biến mất dễ dàng như con trỏ có thể. Vì vậy, chúng không nhất thiết an toàn hơn con trỏ. Các tài liệu tham khảo liên tục và vượt qua có thể nguy hiểm như con trỏ.
Doug T.

6
@Klaim Ý tôi là con trỏ thô. Tôi có nghĩa là C ++ có con trỏ, NULLnullptr, nó có lý do. Và đó không phải là một lời khuyên được cân nhắc hoặc thậm chí thực tế để đưa ra rằng "không bao giờ sử dụng con trỏ" và / hoặc "không bao giờ sử dụng NULL, luôn luôn sử dụng boost::optional". Điều đó thật điên rồ. Đừng hiểu sai ý tôi, con trỏ thô ít cần thiết trong C ++ hơn trong C, nhưng vẫn hữu ích, chúng không "nguy hiểm" như một số người C ++ muốn tuyên bố (đó cũng là một sự cường điệu), và một lần nữa: khi sử dụng một con trỏ dễ dàng hơn và return nullptr;chỉ ra một giá trị bị thiếu ... Tại sao phải nhập toàn bộ Boost?

5
@Klaim "sử dụng NULL là một thực tiễn tồi" - bây giờ điều đó thật vô lý. Và ifđã lỗi thời và người ta nên sử dụng while() { break; }thay thế, phải không? Ngoài ra, đừng lo lắng, tôi đã thấy và làm việc với các cơ sở mã lớn, và vâng, nếu bạn bất cẩn , thì quyền sở hữu là một vấn đề. Không phải nếu bạn tuân theo các quy ước, mặc dù vậy, hãy sử dụng chúng một cách nhất quán và nhận xét và ghi lại mã của bạn. Nhưng sau tất cả, tôi chỉ nên sử dụng C vì tôi quá ngu ngốc với C ++, phải không?

62

Các màn trình diễn hoàn toàn giống nhau, vì các tài liệu tham khảo được thực hiện trong nội bộ như con trỏ. Do đó, bạn không cần phải lo lắng về điều đó.

Không có quy ước được chấp nhận chung về thời điểm sử dụng tài liệu tham khảo và con trỏ. Trong một vài trường hợp, bạn phải trả lại hoặc chấp nhận các tham chiếu (ví dụ, hàm tạo), nhưng khác với việc bạn có thể tự do làm theo ý muốn. Một quy ước khá phổ biến mà tôi gặp phải là sử dụng các tham chiếu khi tham số phải tham chiếu đến một đối tượng và con trỏ hiện có khi giá trị NULL ổn.

Một số quy ước mã hóa (như của Google ) quy định rằng người ta phải luôn sử dụng các con trỏ hoặc các tham chiếu const, bởi vì các tham chiếu có một chút cú pháp không rõ ràng: chúng có hành vi tham chiếu nhưng cú pháp giá trị.


10
Chỉ cần thêm một chút vào điều này, hướng dẫn kiểu của Google nói rằng các tham số đầu vào cho các hàm phải là tham chiếu const và đầu ra phải là con trỏ. Tôi thích điều này bởi vì nó làm cho nó rất rõ ràng khi bạn đọc một chữ ký hàm là đầu vào là gì và đầu ra là gì.
Dan

44
@Dan: Hướng dẫn kiểu Google dành cho mã cũ của Google và không nên được sử dụng cho mã hóa hiện đại. Trên thực tế, đó là một phong cách mã hóa khá tệ cho một dự án mới.
GManNickG

13
@connec: Hãy để tôi đặt nó theo cách này: null là một giá trị con trỏ hoàn toàn hợp lệ . Bất cứ nơi nào có một con trỏ, tôi có thể cung cấp cho nó giá trị null. Vậy thì phiên bản thứ hai của bạn add_onephá vỡ : add_one(0); // passing a perfectly valid pointer value, kaboom. Bạn cần kiểm tra xem nó có rỗng không. Một số người sẽ vặn lại: "tôi sẽ chỉ tài liệu rằng chức năng của tôi không hoạt động với null". Điều đó tốt, nhưng sau đó bạn đánh bại mục đích của câu hỏi: nếu bạn sẽ tìm đến tài liệu để xem null có ổn không, bạn cũng sẽ thấy khai báo hàm .
GManNickG

8
Nếu đó là một tài liệu tham khảo bạn sẽ thấy đó là trường hợp. Mặc dù vậy, một câu trả lời lại bỏ lỡ điểm: Các tham chiếu thực thi ở cấp độ ngôn ngữ mà nó đề cập đến một đối tượng hiện có và không thể là null, trong khi các con trỏ không có hạn chế như vậy. Tôi nghĩ rõ ràng rằng việc thực thi ở cấp độ ngôn ngữ mạnh hơn và ít bị lỗi hơn so với thực thi ở cấp độ tài liệu. Một số người sẽ cố gắng vặn lại điều này bằng cách nói: "Hãy xem, tham chiếu null : int& i = *((int*)0);. Đây không phải là một câu trả lời hợp lệ. Vấn đề trong mã trước nằm ở việc sử dụng con trỏ, không phải với tham chiếu . Tham chiếu không bao giờ là null, giai đoạn.
GManNickG

12
Xin chào, tôi thấy thiếu luật sư ngôn ngữ trong các bình luận vì vậy hãy để tôi khắc phục: tài liệu tham khảo thường được thực hiện bởi con trỏ nhưng tiêu chuẩn nói không có điều đó. Việc thực hiện bằng cách sử dụng một số cơ chế khác sẽ là khiếu nại 100%.
Thomas Bonini

34

Từ C ++ FAQ Lite -

Sử dụng tài liệu tham khảo khi bạn có thể, và con trỏ khi bạn phải.

Tài liệu tham khảo thường được ưa thích hơn con trỏ bất cứ khi nào bạn không cần "nối lại". Điều này thường có nghĩa là các tài liệu tham khảo hữu ích nhất trong giao diện chung của một lớp. Tài liệu tham khảo thường xuất hiện trên da của một đối tượng và con trỏ ở bên trong.

Ngoại lệ ở trên là nơi tham số hoặc giá trị trả về của hàm cần tham chiếu "sentinel" - tham chiếu không tham chiếu đến một đối tượng. Điều này thường được thực hiện tốt nhất bằng cách trả về / lấy một con trỏ và cung cấp cho con trỏ NULL ý nghĩa đặc biệt này (các tham chiếu phải luôn luôn là các đối tượng bí danh, không phải là một con trỏ NULL bị loại bỏ).

Lưu ý: Đôi khi các lập trình viên dòng C cũ không thích tài liệu tham khảo vì họ cung cấp ngữ nghĩa tham chiếu không rõ ràng trong mã của người gọi. Tuy nhiên, sau một số kinh nghiệm về C ++, người ta nhanh chóng nhận ra đây là một hình thức che giấu thông tin, là một tài sản chứ không phải là một trách nhiệm pháp lý. Ví dụ, lập trình viên nên viết mã bằng ngôn ngữ của vấn đề thay vì ngôn ngữ của máy.


1
Tôi đoán bạn có thể lập luận rằng nếu bạn đang sử dụng API, bạn nên làm quen với những gì nó làm và biết liệu tham số đã qua có được sửa đổi hay không ... một cái gì đó để xem xét, nhưng tôi thấy mình đồng ý với các lập trình viên C ( mặc dù tôi có ít kinh nghiệm bản thân C). Tôi muốn thêm mặc dù cú pháp rõ ràng hơn có lợi cho lập trình viên cũng như máy móc.
Connec

1
@connec: Chắc chắn lập trình viên C đã sửa nó cho ngôn ngữ của họ. Nhưng đừng phạm sai lầm khi coi C ++ là C. Đó là một ngôn ngữ hoàn toàn khác. Nếu bạn coi C ++ là C, cuối cùng bạn sẽ viết những gì được coi là tương đương với nhau C with class(không phải là C ++).
Martin York

15

Nguyên tắc của tôi là:

  • Sử dụng con trỏ cho các tham số đi hoặc vào / ra. Vì vậy, có thể thấy rằng giá trị sẽ được thay đổi. (Bạn phải sử dụng &)
  • Sử dụng con trỏ nếu tham số NULL là giá trị chấp nhận được. (Hãy chắc chắn rằng constđó là một tham số đến)
  • Sử dụng tham chiếu cho tham số đến nếu nó không thể là NULL và không phải là kiểu nguyên thủy ( const T&).
  • Sử dụng con trỏ hoặc con trỏ thông minh khi trả về một đối tượng mới được tạo.
  • Sử dụng con trỏ hoặc con trỏ thông minh như các thành viên cấu trúc hoặc lớp thay vì tham chiếu.
  • Sử dụng tài liệu tham khảo cho răng cưa (ví dụ. int &current = someArray[i])

Bất kể bạn sử dụng cái nào, đừng quên ghi lại các chức năng của bạn và ý nghĩa của các tham số nếu chúng không rõ ràng.


14

Tuyên bố miễn trừ trách nhiệm: ngoài thực tế là các tài liệu tham khảo không thể là NULL hay "rebound" (có nghĩa là không thể thay đổi đối tượng là bí danh của nó), nó thực sự liên quan đến vấn đề sở thích, vì vậy tôi sẽ không nói "điều này tốt hơn".

Điều đó nói rằng, tôi không đồng ý với tuyên bố cuối cùng của bạn trong bài đăng, vì tôi không nghĩ rằng mã mất đi sự rõ ràng với các tài liệu tham khảo. Trong ví dụ của bạn,

add_one(&a);

có thể rõ ràng hơn

add_one(a);

vì bạn biết rằng rất có thể giá trị của a sẽ thay đổi. Mặt khác, chữ ký của hàm

void add_one(int* const n);

cũng có phần không rõ ràng: n sẽ là một số nguyên đơn hay một mảng? Đôi khi bạn chỉ có quyền truy cập vào các tiêu đề (tài liệu kém) và chữ ký như

foo(int* const a, int b);

không dễ để giải thích từ cái nhìn đầu tiên.

Imho, tài liệu tham khảo cũng tốt như con trỏ khi không phân bổ (tái) cũng không phải đóng lại (theo nghĩa được giải thích trước đây). Hơn nữa, nếu một nhà phát triển chỉ sử dụng các con trỏ cho các mảng, chữ ký hàm có phần ít mơ hồ hơn. Không đề cập đến thực tế là cú pháp toán tử là cách dễ đọc hơn với các tài liệu tham khảo.


Cảm ơn đã chứng minh rõ ràng về nơi cả hai giải pháp đạt được và mất đi sự rõ ràng. Tôi ban đầu ở trong trại con trỏ, nhưng điều này rất có ý nghĩa.
Zach Beavon-Collin

12

Giống như những người khác đã trả lời: tài liệu tham khảo Luôn sử dụng, trừ khi con người biến NULL/ nullptrthực sự tình trạng hợp lệ.

Quan điểm của John Carmack về chủ đề này là tương tự:

Con trỏ NULL là vấn đề lớn nhất trong C / C ++, ít nhất là trong mã của chúng tôi. Việc sử dụng kép một giá trị duy nhất là cả cờ và địa chỉ gây ra một số vấn đề nghiêm trọng đáng kinh ngạc. Tài liệu tham khảo C ++ nên được ưa chuộng hơn con trỏ bất cứ khi nào có thể; trong khi một tài liệu tham khảo là thực sự chỉ là một con trỏ, nó có hợp đồng ngầm là không phải là NULL. Thực hiện kiểm tra NULL khi con trỏ được chuyển thành tham chiếu, sau đó bạn có thể bỏ qua vấn đề sau đó.

http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

Chỉnh sửa 2012/03/13

Người dùng Bret Kuhns nhận xét đúng:

Tiêu chuẩn C ++ 11 đã được hoàn thiện. Tôi nghĩ rằng đã đến lúc trong chủ đề này để đề cập rằng hầu hết các mã sẽ làm rất tốt với sự kết hợp của các tham chiếu, shared_ptr và unique_ptr.

Đúng như vậy, nhưng câu hỏi vẫn còn, ngay cả khi thay thế con trỏ thô bằng con trỏ thông minh.

Ví dụ, cả hai std::unique_ptrstd::shared_ptrcó thể được xây dựng dưới dạng con trỏ "trống" thông qua hàm tạo mặc định của chúng:

... có nghĩa là việc sử dụng chúng mà không cần xác minh chúng không có nguy cơ xảy ra sự cố, đó chính xác là những gì cuộc thảo luận của J. Carmack nói về.

Và sau đó, chúng ta có một vấn đề thú vị là "làm thế nào để chúng ta vượt qua một con trỏ thông minh như một tham số hàm?"

Câu trả lời của Jon cho câu hỏi C ++ - chuyển tham chiếu để boost :: shared_ptr và các nhận xét sau đây cho thấy ngay cả khi đó, việc chuyển một con trỏ thông minh bằng bản sao hoặc bằng cách tham chiếu không rõ ràng như mọi người mong muốn (tôi ủng hộ bản thân " theo tham chiếu "theo mặc định, nhưng tôi có thể sai).


1
Tiêu chuẩn C ++ 11 đã được hoàn thiện. Tôi nghĩ rằng đã đến lúc trong chủ đề này để đề cập rằng hầu hết các mã sẽ làm rất tốt với sự kết hợp của các tham chiếu shared_ptr, và unique_ptr. Ngữ nghĩa quyền sở hữu và các quy ước tham số vào / ra được quan tâm bởi sự kết hợp của ba phần và cấu trúc này. Hầu như không cần con trỏ thô trong C ++ ngoại trừ khi xử lý mã kế thừa và các thuật toán được tối ưu hóa. Những khu vực mà chúng được sử dụng phải được gói gọn hết mức có thể và chuyển đổi bất kỳ con trỏ thô nào thành tương đương "hiện đại" phù hợp về mặt ngữ nghĩa.
Bret Kuhns

1
Rất nhiều thời gian con trỏ thông minh không nên được thông qua, nhưng nên được kiểm tra tính vô hiệu và sau đó đối tượng chứa của chúng được truyền qua tham chiếu. Lần duy nhất bạn thực sự nên vượt qua một con trỏ thông minh là khi bạn chuyển quyền sở hữu (unique_ptr) hoặc chia sẻ (shared_ptr) với một đối tượng khác.
Luke Worth

@povman: Tôi hoàn toàn đồng ý: Nếu quyền sở hữu không phải là một phần của giao diện (và trừ khi nó sắp được sửa đổi, thì không nên), thì chúng ta không nên chuyển một con trỏ thông minh làm tham số (hoặc giá trị trả về). Điều phức tạp hơn một chút khi quyền sở hữu là một phần của giao diện. Ví dụ, cuộc tranh luận về Sutter / Meyers về cách vượt qua unique_ptr làm tham số: bằng cách sao chép (Sutter) hoặc bằng tham chiếu giá trị r (Meyers)? Một antipotype dựa vào việc chuyển xung quanh một con trỏ tới shared_ptr toàn cầu, với nguy cơ con trỏ đó bị vô hiệu (giải pháp sao chép con trỏ thông minh trên ngăn xếp)
paercebal

7

Đó không phải là vấn đề của hương vị. Dưới đây là một số quy tắc dứt khoát.

Nếu bạn muốn tham chiếu đến một biến được khai báo tĩnh trong phạm vi mà nó được khai báo thì hãy sử dụng tham chiếu C ++ và nó sẽ hoàn toàn an toàn. Điều tương tự áp dụng cho một con trỏ thông minh được khai báo tĩnh. Truyền tham số bằng tham chiếu là một ví dụ về cách sử dụng này.

Nếu bạn muốn tham chiếu bất cứ điều gì từ một phạm vi rộng hơn phạm vi mà nó được khai báo thì bạn nên sử dụng một con trỏ thông minh được tính tham chiếu cho nó để hoàn toàn an toàn.

Bạn có thể tham khảo một yếu tố của một bộ sưu tập với một tham chiếu cho thuận tiện cú pháp, nhưng nó không an toàn; các yếu tố có thể bị xóa bất cứ lúc nào.

Để giữ an toàn một tham chiếu đến một phần tử của bộ sưu tập, bạn phải sử dụng một con trỏ thông minh được tính tham chiếu.


5

Bất kỳ sự khác biệt hiệu suất nào cũng sẽ nhỏ đến mức nó sẽ không biện minh cho việc sử dụng cách tiếp cận ít rõ ràng hơn.

Đầu tiên, một trường hợp không được đề cập trong đó các tài liệu tham khảo thường vượt trội là consttài liệu tham khảo. Đối với các loại không đơn giản, việc vượt qua const referencesẽ tránh tạo tạm thời và không gây ra sự nhầm lẫn mà bạn quan tâm (vì giá trị không được sửa đổi). Ở đây, việc buộc một người phải vượt qua một con trỏ gây ra sự nhầm lẫn mà bạn lo lắng, vì việc nhìn thấy địa chỉ được lấy và chuyển đến một chức năng có thể khiến bạn nghĩ rằng giá trị đã thay đổi.

Trong mọi trường hợp, tôi về cơ bản đồng ý với bạn. Tôi không thích các hàm lấy tham chiếu để sửa đổi giá trị của chúng khi không rõ ràng rằng đây là chức năng đang làm. Tôi cũng thích sử dụng con trỏ trong trường hợp đó.

Khi bạn cần trả về một giá trị trong một loại phức tạp, tôi có xu hướng thích các tài liệu tham khảo. Ví dụ:

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

Ở đây, tên hàm làm rõ rằng bạn đang lấy lại thông tin trong một mảng. Vì vậy, không có nhầm lẫn.

Ưu điểm chính của tài liệu tham khảo là chúng luôn chứa một giá trị hợp lệ, sạch hơn con trỏ và hỗ trợ đa hình mà không cần bất kỳ cú pháp bổ sung nào. Nếu không có ưu điểm nào trong số này áp dụng, không có lý do gì để thích tham chiếu hơn con trỏ.


4

Sao chép từ wiki -

Hậu quả của điều này là trong nhiều triển khai, hoạt động trên một biến có thời gian tồn tại tự động hoặc tĩnh thông qua một tham chiếu, mặc dù về mặt cú pháp tương tự như truy cập trực tiếp, có thể liên quan đến các hoạt động vô hiệu hóa ẩn rất tốn kém. Tài liệu tham khảo là một tính năng gây tranh cãi về mặt cú pháp của C ++ vì chúng che khuất mức độ không xác định của định danh; nghĩa là, không giống như mã C nơi con trỏ thường nổi bật về mặt cú pháp, trong một khối lớn mã C ++, có thể không rõ ràng ngay lập tức nếu đối tượng được truy cập được định nghĩa là biến cục bộ hoặc biến toàn cục hoặc cho dù đó là tham chiếu (con trỏ ẩn) một số vị trí khác, đặc biệt nếu mã trộn các tham chiếu và con trỏ. Khía cạnh này có thể làm cho mã C ++ được viết kém khó đọc và gỡ lỗi hơn (xem Bí danh).

Tôi đồng ý 100% với điều này, và đây là lý do tại sao tôi tin rằng bạn chỉ nên sử dụng tài liệu tham khảo khi bạn có lý do rất chính đáng để làm việc đó.


Tôi cũng đồng ý ở một mức độ lớn, tuy nhiên tôi sẽ hiểu rằng việc mất bảo vệ tích hợp chống lại các con trỏ NULL là quá tốn kém cho các mối quan tâm cú pháp thuần túy, đặc biệt là - mặc dù rõ ràng hơn - cú pháp con trỏ khá xấu xí dù sao.
connec

Tôi cho rằng hoàn cảnh cũng là một yếu tố quan trọng. Tôi nghĩ rằng cố gắng sử dụng các tham chiếu khi cơ sở mã hiện tại chủ yếu sử dụng các con trỏ sẽ là một ý tưởng tồi. Nếu bạn mong đợi chúng là tài liệu tham khảo thì thực tế là chúng rất ít quan trọng có thể ..
user606723

3

Những điểm cần lưu ý:

  1. Con trỏ có thể NULL, tài liệu tham khảo không thể NULL.

  2. Tài liệu tham khảo dễ sử dụng hơn, constcó thể được sử dụng để tham khảo khi chúng tôi không muốn thay đổi giá trị và chỉ cần một tài liệu tham khảo trong một chức năng.

  3. Con trỏ được sử dụng với một *tham chiếu trong một thời gian được sử dụng với a &.

  4. Sử dụng con trỏ khi hoạt động số học con trỏ được yêu cầu.

  5. Bạn có thể có con trỏ tới loại khoảng trống int a=5; void *p = &a;nhưng không thể có tham chiếu đến loại khoảng trống.

Tham khảo con trỏ Vs

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

Phán quyết khi nào nên sử dụng cái gì

Con trỏ : Dành cho mảng, danh sách liên kết, triển khai cây và số học con trỏ.

Tham khảo : Trong các tham số chức năng và các loại trả về.


2

Có vấn đề với quy tắc " sử dụng tài liệu tham khảo bất cứ nơi nào có thể " và nó phát sinh nếu bạn muốn tiếp tục tham khảo để sử dụng tiếp. Để minh họa điều này với ví dụ, hãy tưởng tượng bạn có các lớp sau.

class SimCard
{
    public:
        explicit SimCard(int id):
            m_id(id)
        {
        }

        int getId() const
        {
            return m_id;
        }

    private:
        int m_id;
};

class RefPhone
{
    public:
        explicit RefPhone(const SimCard & card):
            m_card(card)
        {
        }

        int getSimId()
        {
            return m_card.getId();
        }

    private:
        const SimCard & m_card;
};

Lúc đầu, có vẻ là một ý tưởng tốt khi có tham số trong hàm RefPhone(const SimCard & card)tạo được truyền bởi một tham chiếu, bởi vì nó ngăn chặn truyền con trỏ sai / null cho hàm tạo. Nó bằng cách nào đó khuyến khích phân bổ các biến trên stack và nhận lợi ích từ RAII.

PtrPhone nullPhone(0);  //this will not happen that easily
SimCard * cardPtr = new SimCard(666);  //evil pointer
delete cardPtr;  //muahaha
PtrPhone uninitPhone(cardPtr);  //this will not happen that easily

Nhưng sau đó tạm thời đến để phá hủy thế giới hạnh phúc của bạn.

RefPhone tempPhone(SimCard(666));   //evil temporary
//function referring to destroyed object
tempPhone.getSimId();    //this can happen

Vì vậy, nếu bạn mù quáng bám vào các tài liệu tham khảo, bạn đánh đổi khả năng chuyển các con trỏ không hợp lệ cho khả năng lưu trữ các tham chiếu đến các đối tượng bị phá hủy, về cơ bản có tác dụng tương tự.

chỉnh sửa: Lưu ý rằng tôi đã tuân thủ quy tắc "Sử dụng tài liệu tham khảo bất cứ nơi nào bạn có thể, con trỏ bất cứ nơi nào bạn phải. Tránh con trỏ cho đến khi bạn không thể." từ câu trả lời được đánh giá cao nhất và được chấp nhận (câu trả lời khác cũng gợi ý như vậy). Mặc dù nó là hiển nhiên, ví dụ không phải là để chỉ ra rằng các tài liệu tham khảo như vậy là xấu. Tuy nhiên, chúng có thể bị sử dụng sai, giống như con trỏ và chúng có thể mang lại các mối đe dọa riêng cho mã.


Có sự khác biệt sau đây giữa con trỏ và tài liệu tham khảo.

  1. Khi nói đến việc chuyển các biến, chuyển qua tham chiếu trông giống như truyền theo giá trị, nhưng có ngữ nghĩa con trỏ (hoạt động giống như con trỏ).
  2. Tham chiếu không thể được khởi tạo trực tiếp thành 0 (null).
  3. Tham chiếu (tham chiếu, không tham chiếu đối tượng) không thể được sửa đổi (tương đương với con trỏ "* const").
  4. tham chiếu const có thể chấp nhận tham số tạm thời.
  5. Tài liệu tham khảo const địa phương kéo dài tuổi thọ của các đối tượng tạm thời

Đưa những điều đó vào tài khoản hiện tại của tôi như sau.

  • Sử dụng tài liệu tham khảo cho các tham số sẽ được sử dụng cục bộ trong phạm vi chức năng.
  • Sử dụng con trỏ khi 0 (null) là giá trị tham số chấp nhận được hoặc bạn cần lưu trữ tham số để sử dụng tiếp. Nếu 0 (null) có thể chấp nhận được, tôi đang thêm hậu tố "_n" vào tham số, sử dụng con trỏ được bảo vệ (như QPulum trong Qt) hoặc chỉ ghi lại nó. Bạn cũng có thể sử dụng con trỏ thông minh. Bạn phải cẩn thận hơn với các con trỏ được chia sẻ so với các con trỏ bình thường (nếu không bạn có thể bị rò rỉ bộ nhớ thiết kế và lộn xộn trách nhiệm).

3
Vấn đề với ví dụ của bạn không phải là các tài liệu tham khảo không an toàn, mà là bạn đang dựa vào một cái gì đó nằm ngoài phạm vi của thể hiện đối tượng của bạn để giữ cho các thành viên riêng của bạn tồn tại. const SimCard & m_card;chỉ là một mã viết xấu.
plamenko

@plamenko Tôi sợ bạn không hiểu mục đích của ví dụ. Có const SimCard & m_cardđúng hay không phụ thuộc vào ngữ cảnh. Thông điệp trong bài viết này không phải là các tài liệu tham khảo không an toàn (tho 'chúng có thể là nếu một người cố gắng hết sức). Thông điệp là bạn không nên mù quáng "sử dụng tài liệu tham khảo bất cứ khi nào có thể". Ví dụ là kết quả của việc sử dụng học thuyết "sử dụng tài liệu tham khảo bất cứ khi nào có thể". Điều này cần được rõ ràng.
tài liệu

Có hai điều khiến tôi bận tâm với câu trả lời của bạn vì tôi nghĩ nó có thể đánh lừa ai đó đang cố gắng tìm hiểu thêm về vấn đề này. 1. Bài đăng là một hướng và thật dễ dàng để có ấn tượng rằng các tài liệu tham khảo là xấu. Bạn chỉ cung cấp một ví dụ duy nhất về cách không sử dụng tài liệu tham khảo. 2. Bạn không rõ ràng trong ví dụ của bạn những gì sai với nó. Vâng, tạm thời sẽ nhận được hủy, nhưng đó không phải là dòng sai, đó là việc thực hiện lớp.
plamenko

Bạn thực tế không bao giờ có thành viên như thế const SimCard & m_card. Nếu bạn muốn hiệu quả với tạm thời, hãy thêm explicit RefPhone(const SimCard&& card)constructor.
plamenko

@plamenko nếu bạn không thể đọc với một số hiểu cơ bản thì bạn có vấn đề lớn hơn là chỉ bị bài viết của tôi đánh lừa. Tôi không biết làm thế nào tôi có thể rõ ràng hơn. Nhìn vào câu đầu tiên. Có một vấn đề với câu thần chú "sử dụng bất cứ khi nào có thể"! Trường hợp trong bài viết của tôi, bạn đã tìm thấy một tuyên bố rằng tài liệu tham khảo là xấu? Cuối bài viết của tôi, bạn đã viết nơi để sử dụng tài liệu tham khảo, vậy làm thế nào bạn đi kèm với kết luận như vậy? Đây không phải là một câu trả lời trực tiếp cho câu hỏi?
tài liệu

1

Sau đây là một số hướng dẫn.

Một hàm sử dụng dữ liệu được truyền mà không sửa đổi nó:

  1. Nếu đối tượng dữ liệu nhỏ, chẳng hạn như kiểu dữ liệu tích hợp hoặc cấu trúc nhỏ, hãy chuyển nó theo giá trị.

  2. Nếu đối tượng dữ liệu là một mảng, hãy sử dụng một con trỏ bởi vì đó là lựa chọn duy nhất của bạn. Làm cho con trỏ một con trỏ để const.

  3. Nếu đối tượng dữ liệu là cấu trúc có kích thước tốt, hãy sử dụng con trỏ const hoặc tham chiếu const để tăng hiệu quả chương trình. Bạn tiết kiệm thời gian và không gian cần thiết để sao chép cấu trúc hoặc thiết kế lớp. Tạo con trỏ hoặc tham chiếu const.

  4. Nếu đối tượng dữ liệu là đối tượng lớp, hãy sử dụng tham chiếu const. Ngữ nghĩa của thiết kế lớp thường yêu cầu sử dụng tham chiếu, đó là lý do chính C ++ đã thêm tính năng này. Vì vậy, cách tiêu chuẩn để truyền đối số đối tượng lớp là tham chiếu.

Một chức năng sửa đổi dữ liệu trong chức năng gọi:

1.Nếu đối tượng dữ liệu là kiểu dữ liệu tích hợp, hãy sử dụng một con trỏ. Nếu bạn phát hiện mã như fixit (& x), trong đó x là int, thì rõ ràng hàm này có ý định sửa đổi x.

2.Nếu đối tượng dữ liệu là một mảng, hãy sử dụng lựa chọn duy nhất của bạn: một con trỏ.

3.Nếu đối tượng dữ liệu là cấu trúc, hãy sử dụng tham chiếu hoặc con trỏ.

4.Nếu đối tượng dữ liệu là một đối tượng lớp, hãy sử dụng một tham chiếu.

Tất nhiên, đây chỉ là những hướng dẫn và có thể có những lý do để đưa ra những lựa chọn khác nhau. Ví dụ: cin sử dụng tài liệu tham khảo cho các loại cơ bản để bạn có thể sử dụng cin >> n thay vì cin >> & n.


0

Tài liệu tham khảo sạch hơn và dễ sử dụng hơn, và chúng làm tốt hơn việc che giấu thông tin. Tài liệu tham khảo không thể được chỉ định lại, tuy nhiên. Nếu bạn cần trỏ đầu tiên đến một đối tượng và sau đó đến một đối tượng khác, bạn phải sử dụng một con trỏ. Tài liệu tham khảo không thể là null, vì vậy nếu có bất kỳ cơ hội nào mà đối tượng trong câu hỏi có thể là null, bạn không được sử dụng tài liệu tham khảo. Bạn phải sử dụng một con trỏ. Nếu bạn muốn tự mình xử lý thao tác đối tượng, nếu bạn muốn phân bổ không gian bộ nhớ cho một đối tượng trên Heap thay vì trên Stack, bạn phải sử dụng Con trỏ

int *pInt = new int; // allocates *pInt on the Heap

0

Bạn viết ví dụ đúng nên trông như thế

void add_one(int& n) { n += 1; }
void add_one(int* const n)
{
  if (n)
    *n += 1;
}

Đó là lý do tại sao các tài liệu tham khảo được ưa thích hơn nếu có thể ...


-1

Chỉ cần đặt xu của tôi vào. Tôi vừa thực hiện một bài kiểm tra. Một cái hắt xì lúc đó. Tôi chỉ để g ++ tạo các tệp lắp ráp của cùng một chương trình nhỏ bằng cách sử dụng các con trỏ so với sử dụng các tham chiếu. Khi nhìn vào đầu ra chúng giống hệt nhau. Khác với tên tượng trưng. Vì vậy, nhìn vào hiệu suất (trong một ví dụ đơn giản) không có vấn đề.

Bây giờ về chủ đề con trỏ so với tài liệu tham khảo. IMHO Tôi nghĩ rằng sự rõ ràng đứng trên tất cả. Ngay khi tôi đọc hành vi ngầm, các ngón chân của tôi bắt đầu cong. Tôi đồng ý rằng đó là hành vi ngầm tốt đẹp mà một tài liệu tham khảo không thể là NULL.

Hủy bỏ hội nghị một con trỏ NULL không phải là vấn đề. nó sẽ làm hỏng ứng dụng của bạn và sẽ dễ dàng gỡ lỗi. Một vấn đề lớn hơn là con trỏ chưa được khởi tạo có chứa các giá trị không hợp lệ. Điều này rất có thể sẽ dẫn đến tham nhũng bộ nhớ gây ra hành vi không xác định mà không có nguồn gốc rõ ràng.

Đây là nơi tôi nghĩ rằng tài liệu tham khảo an toàn hơn nhiều so với con trỏ. Và tôi đồng ý với một tuyên bố trước đó, rằng giao diện (cần được ghi lại rõ ràng, xem thiết kế theo hợp đồng, Bertrand Meyer) xác định kết quả của các tham số cho một chức năng. Bây giờ xem xét tất cả các sở thích của tôi đi đến việc sử dụng tài liệu tham khảo bất cứ nơi nào / bất cứ khi nào có thể.


-2

Đối với con trỏ, bạn cần chúng để trỏ đến một cái gì đó, vì vậy con trỏ tốn không gian bộ nhớ.

Ví dụ, một hàm lấy một con trỏ nguyên sẽ không lấy biến số nguyên. Vì vậy, bạn sẽ cần phải tạo một con trỏ cho cái đó trước tiên để truyền vào hàm.

Đối với một tài liệu tham khảo, nó sẽ không tốn bộ nhớ. Bạn có một biến số nguyên và bạn có thể chuyển nó dưới dạng biến tham chiếu. Đó là nó. Bạn không cần phải tạo một biến tham chiếu đặc biệt cho nó.


4
Không. Hàm lấy con trỏ không yêu cầu cấp phát biến con trỏ: bạn có thể vượt qua tạm thời &address. Một tham chiếu chắc chắn sẽ tốn bộ nhớ nếu nó là thành viên của một đối tượng và hơn nữa, tất cả các trình biên dịch còn tồn tại thực sự thực hiện các tham chiếu dưới dạng địa chỉ, do đó bạn không tiết kiệm được gì về mặt truyền tham số hoặc hội thảo.
gạch dưới
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.