Tại sao các thư viện và khung C ++ không bao giờ sử dụng con trỏ thông minh?


156

Tôi đã đọc trong một vài bài viết rằng con trỏ thô gần như không bao giờ được sử dụng. Thay vào đó, chúng phải luôn được bọc bên trong các con trỏ thông minh, cho dù đó là con trỏ có phạm vi hoặc chia sẻ.

Tuy nhiên, tôi nhận thấy rằng các khung như Qt, wxWidgets và các thư viện như Boost không bao giờ quay trở lại cũng không mong đợi các con trỏ thông minh, như thể chúng hoàn toàn không sử dụng chúng. Thay vào đó, họ trở lại hoặc mong đợi con trỏ thô. Có bất kỳ lý do cho điều đó? Tôi có nên tránh xa các con trỏ thông minh khi tôi viết API công khai không, và tại sao?

Chỉ tự hỏi tại sao con trỏ thông minh được khuyến khích khi nhiều dự án lớn dường như tránh chúng.


22
Tất cả những thư viện mà bạn vừa đặt tên đã được bắt đầu từ nhiều năm trước. Con trỏ thông minh chỉ thực sự trở thành tiêu chuẩn trong C ++ 11.
chrisaycock

22
con trỏ thông minh có một chi phí chung (đếm tham chiếu, v.v.) - có thể rất quan trọng - chẳng hạn trong các hệ thống nhúng / thời gian thực. IMHO - con trỏ thông minh dành cho các lập trình viên lười biếng. Ngoài ra, rất nhiều API dành cho mẫu số chung thấp nhất. Tôi cảm thấy ngọn lửa liếm quanh chân khi tôi gõ!
Ed Heal

93
@EdHeal: Lý do bạn có thể cảm thấy ngọn lửa liếm quanh chân là vì bạn hoàn toàn sai về mọi khía cạnh. Ví dụ, những gì trên đầu là trong unique_ptr? Không có gì. Qt / WxWidgets được nhắm mục tiêu tại các hệ thống nhúng hoặc thời gian thực? Không, chúng dành cho Windows / Mac / Unix trên máy tính để bàn - nhiều nhất là. Con trỏ thông minh dành cho các lập trình viên muốn làm cho nó chính xác.
Cún con

24
Thực sự, điện thoại di động đang chạy Java.
R. Martinho Fernandes

12
Con trỏ thông minh chỉ thực sự chuẩn trong C ++ 11? Gì??? Những thứ này đã được sử dụng trong hơn 20 năm.
Kaz

Câu trả lời:


124

Ngoài thực tế là nhiều thư viện đã được viết trước khi có con trỏ thông minh tiêu chuẩn, lý do lớn nhất có lẽ là thiếu Giao diện nhị phân ứng dụng C ++ tiêu chuẩn (ABI).

Nếu bạn đang viết một thư viện chỉ có tiêu đề, bạn có thể chuyển xung quanh các con trỏ thông minh và các thùng chứa tiêu chuẩn cho nội dung trái tim của bạn. Nguồn của họ có sẵn cho thư viện của bạn vào thời gian biên dịch, vì vậy bạn chỉ dựa vào tính ổn định của giao diện của họ chứ không phải việc triển khai của họ.

Nhưng do thiếu ABI tiêu chuẩn, bạn thường không thể vượt qua các đối tượng này một cách an toàn qua các ranh giới mô-đun. GCC shared_ptrcó lẽ khác với MSVC shared_ptr, cũng có thể khác với Intel shared_ptr. Ngay cả với cùng một trình biên dịch, các lớp này không được đảm bảo tương thích nhị phân giữa các phiên bản.

Điểm mấu chốt là nếu bạn muốn phân phối một phiên bản dựng sẵn của thư viện của mình, bạn cần có một ABI tiêu chuẩn để dựa vào. C không có một, nhưng các nhà cung cấp trình biên dịch rất tốt về khả năng tương tác giữa các thư viện C cho một nền tảng nhất định, có các tiêu chuẩn thực tế.

Tình hình không tốt cho C ++. Các trình biên dịch riêng lẻ có thể xử lý sự tương tác giữa các nhị phân riêng của chúng, vì vậy bạn có tùy chọn phân phối một phiên bản cho mọi trình biên dịch được hỗ trợ, thường là GCC và MSVC. Nhưng theo quan điểm này, hầu hết các thư viện chỉ xuất một giao diện C và có nghĩa là con trỏ thô.

Mã phi thư viện, tuy nhiên, thường thích con trỏ thông minh hơn thô.


17
Tôi đồng ý với bạn, thậm chí việc truyền chuỗi std :: có thể là một nỗi đau, Điều này nói rất nhiều về C ++ như là một "ngôn ngữ tuyệt vời cho các thư viện".
Ha11owed

8
Điểm mấu chốt giống như: nếu bạn muốn phân phối một phiên bản prebuild, bạn phải làm như vậy cho mọi trình biên dịch bạn muốn hỗ trợ.
josefx

6
@josefx: Có, điều này thật đáng buồn nhưng sự thật, sự thay thế duy nhất là COM hoặc giao diện C thô. Tôi ước rằng sự đồng cảm của C ++ sẽ bắt đầu đáng lo ngại cho loại vấn đề này. Ý tôi là không giống như C ++ là ngôn ngữ mới từ 2 năm trước.
Robot Mess

3
Tôi đánh giá thấp vì điều này là sai. Các vấn đề ABI không thể quản lý được trong hầu hết các trường hợp. Mặc dù hầu như không thân thiện với người dùng, ABI cũng khó có thể vượt qua.
Cún con

4
@NathanAdams: Phần mềm như vậy chắc chắn rất ấn tượng và hữu ích. Nhưng nó xử lý các triệu chứng của các vấn đề sâu sắc hơn: ngữ nghĩa của C ++ về thời gian sống và quyền sở hữu nằm ở đâu đó giữa nghèo nàn và không tồn tại. Những lỗi heap đó sẽ không phát sinh nếu ngôn ngữ không cho phép chúng. Vì vậy, chắc chắn, con trỏ thông minh không phải là thuốc chữa bách bệnh. Chúng là một nỗ lực để bù lại một số tổn thất phát sinh khi sử dụng C ++ ở nơi đầu tiên.
Jon Purdy

40

Có thể có nhiều lý do. Để liệt kê một vài trong số họ:

  1. Con trỏ thông minh đã trở thành một phần của tiêu chuẩn chỉ gần đây. Cho đến khi họ là một phần của các thư viện khác
  2. Công dụng chính của chúng là tránh rò rỉ bộ nhớ; nhiều thư viện không có quản lý bộ nhớ riêng; Nói chung, họ cung cấp các tiện ích và API
  3. Chúng được thực hiện như là trình bao bọc, vì chúng thực sự là các đối tượng và không phải là con trỏ. Mà có thêm chi phí thời gian / không gian, so với con trỏ thô; Người dùng của các thư viện có thể không muốn có các chi phí như vậy

Chỉnh sửa : Sử dụng con trỏ thông minh là sự lựa chọn hoàn toàn của nhà phát triển. Nó phụ thuộc vào các yếu tố khác nhau.

  1. Trong các hệ thống quan trọng về hiệu năng, bạn có thể không muốn sử dụng các con trỏ thông minh tạo ra chi phí chung

  2. Dự án cần khả năng tương thích ngược, bạn có thể không muốn sử dụng các con trỏ thông minh có các tính năng cụ thể của C ++ 11

Edit2 Có một chuỗi gồm nhiều downvote trong khoảng 24 giờ vì đoạn dưới đây. Tôi không hiểu tại sao câu trả lời bị bỏ qua mặc dù bên dưới chỉ là một gợi ý bổ trợ chứ không phải là câu trả lời.
Tuy nhiên, C ++ luôn tạo điều kiện cho bạn mở các tùy chọn. :) ví dụ

template<typename T>
struct Pointer {
#ifdef <Cpp11>
  typedef std::unique_ptr<T> type;
#else
  typedef T* type;
#endif
};

Và trong mã của bạn sử dụng nó như:

Pointer<int>::type p;

Đối với những người nói rằng một con trỏ thông minh và một con trỏ thô là khác nhau, tôi đồng ý với điều đó. Đoạn mã trên chỉ là một ý tưởng trong đó người ta có thể viết một mã có thể hoán đổi cho nhau chỉ với một #define, đây không phải là bắt buộc ;

Ví dụ, T*phải được xóa một cách rõ ràng nhưng một con trỏ thông minh thì không. Chúng ta có thể có một templated Destroy()để xử lý đó.

template<typename T>
void Destroy (T* p)
{
  delete p;
}
template<typename T>
void Destroy (std::unique_ptr<T> p)
{
  // do nothing
}

và sử dụng nó như:

Destroy(p);

Theo cách tương tự, đối với một con trỏ thô, chúng ta có thể sao chép nó trực tiếp và đối với con trỏ thông minh, chúng ta có thể sử dụng thao tác đặc biệt.

Pointer<X>::type p = new X;
Pointer<X>::type p2(Assign(p));

Trong trường hợp Assign()là như sau:

template<typename T>
T* Assign (T *p)
{
  return p;
}
template<typename T>
... Assign (SmartPointer<T> &p)
{
  // use move sematics or whateve appropriate
}

14
Trên 3. Một số con trỏ thông minh có thêm chi phí thời gian / không gian, một số khác thì không, kể cả std::auto_ptrđó là một phần của tiêu chuẩn trong một thời gian dài (và lưu ý, tôi thích std::auto_ptrlàm kiểu trả về cho các hàm tạo đối tượng, ngay cả khi đó là gần như vô dụng ở mọi nơi khác). Trong C ++ 11 std::unique_ptrkhông có chi phí bổ sung so với một con trỏ đơn giản.
David Rodríguez - dribeas

4
Chính xác ... có một sự đối xứng đẹp về sự xuất hiện unique_ptrvà biến mất của auto_ptr, mã nhắm mục tiêu C ++ 03 nên sử dụng sau này, trong khi mã nhắm mục tiêu C ++ 11 có thể sử dụng mã trước. Con trỏ thông minh thì không shared_ptr , có nhiều tiêu chuẩn và không có tiêu chuẩn, bao gồm các đề xuất cho tiêu chuẩn đã bị từ chối nhưmanaged_ptr
David Rodríguez - dribeas

2
@iammilind, đó là những điểm thú vị, nhưng điều buồn cười là nếu cuối cùng chúng ta sử dụng con trỏ thông minh, như rõ ràng nhiều người sẽ khuyên, cuối cùng chúng ta sẽ tạo mã không tương thích với các thư viện lớn. Tất nhiên, chúng ta có thể bọc / mở khóa các con trỏ thông minh khi cần nhưng có vẻ như rất nhiều rắc rối và sẽ tạo ra mã không nhất quán (đôi khi chúng ta xử lý các con trỏ thông minh, đôi khi thì không).
nguyệt quế

7
Tuyên bố rằng con trỏ thông minh có "chi phí thời gian / không gian bổ sung" là hơi sai lệch; tất cả các con trỏ thông minh ngoại trừ unique_ptrchi phí thời gian chạy, nhưng unique_ptrcho đến nay là một trong những con trỏ được sử dụng phổ biến nhất. Mẫu mã bạn cung cấp cũng gây hiểu nhầm, bởi vì unique_ptrT*là các khái niệm hoàn toàn khác nhau. Thực tế là bạn đề cập đến cả hai như thể typemang lại ấn tượng rằng họ có thể hoán đổi cho nhau.
void-con trỏ

12
Bạn không thể gõ typedef như thế, những loại này không theo bất kỳ cách nào tương đương. Viết typedefs như thế này là yêu cầu rắc rối.
Alex B

35

Có hai vấn đề với con trỏ thông minh (trước C ++ 11):

  • không theo tiêu chuẩn, vì vậy mỗi thư viện có xu hướng phát minh lại (vấn đề phụ thuộc và hội đồng NIH)
  • chi phí tiềm năng

Con trỏ thông minh mặc định , trong đó là miễn phí, là unique_ptr. Thật không may, nó đòi hỏi ngữ nghĩa di chuyển C ++ 11, chỉ xuất hiện gần đây. Tất cả các con trỏ thông minh khác có chi phí ( shared_ptr, intrusive_ptr) hoặc có ít hơn ngữ nghĩa lý tưởng ( auto_ptr).

Với C ++ 11 quanh quẩn, mang đến một std::unique_ptr, người ta sẽ bị cám dỗ khi nghĩ rằng cuối cùng nó cũng kết thúc ... Tôi không quá lạc quan.

Chỉ có một vài trình biên dịch chính thực hiện hầu hết C ++ 11 và chỉ trong các phiên bản gần đây của chúng. Chúng ta có thể hy vọng các thư viện lớn như QT và Boost sẽ sẵn sàng duy trì khả năng tương thích với C ++ 03 trong một thời gian, điều này phần nào ngăn cản việc áp dụng rộng rãi các con trỏ thông minh mới và sáng bóng.


12

Bạn không nên tránh xa các con trỏ thông minh, chúng có công dụng của chúng đặc biệt là trong các ứng dụng mà bạn phải vượt qua một đối tượng xung quanh.

Các thư viện có xu hướng hoặc chỉ trả về một giá trị hoặc điền vào một đối tượng. Chúng thường không có các đối tượng cần được sử dụng ở nhiều nơi, vì vậy chúng không cần sử dụng các con trỏ thông minh (ít nhất là không có trong giao diện của chúng, chúng có thể sử dụng chúng trong nội bộ).

Tôi có thể lấy ví dụ như một thư viện mà chúng tôi đang làm việc, sau vài tháng phát triển tôi nhận ra chúng tôi chỉ sử dụng con trỏ và con trỏ thông minh trong một vài lớp (3-5% của tất cả các lớp).

Truyền các biến bằng tham chiếu là đủ ở hầu hết các nơi, chúng tôi đã sử dụng các con trỏ thông minh bất cứ khi nào chúng tôi có một đối tượng có thể là null và các con trỏ thô khi một thư viện mà chúng tôi sử dụng buộc chúng tôi phải sử dụng.

Chỉnh sửa (Tôi không thể nhận xét vì danh tiếng của mình): truyền các biến bằng tham chiếu rất linh hoạt: nếu bạn muốn đối tượng chỉ đọc, bạn có thể sử dụng tham chiếu const (bạn vẫn có thể thực hiện một số phôi khó chịu để có thể viết đối tượng ) nhưng bạn có được sự bảo vệ tối đa có thể (tương tự với con trỏ thông minh). Nhưng tôi đồng ý rằng việc trả lại đối tượng sẽ đẹp hơn nhiều.


Chính xác thì tôi không đồng ý với bạn, nhưng tôi sẽ chỉ ra rằng có một trường phái suy nghĩ không tán thành việc chuyển các tham chiếu biến trong hầu hết các trường hợp. Tôi thú nhận rằng tôi tuân thủ trường đó. Tôi thích các chức năng không sửa đổi đối số của họ. Ở bất cứ giá nào, theo như tôi biết, các tham chiếu biến của C ++ không làm gì để ngăn chặn việc xử lý sai các đối tượng mà chúng tham chiếu, đó là điều mà các con trỏ thông minh dự định làm.
thb

2
bạn có const cho điều đó (dường như tôi có thể nhận xét: D).
Robot Mess

9

Qt đã vô tình phát minh lại nhiều phần của thư viện Standard trong nỗ lực trở thành Java. Tôi tin rằng nó thực sự có con trỏ thông minh của riêng mình bây giờ, nhưng nói chung, nó hầu như không phải là đỉnh cao của thiết kế. wxWidgets, theo như tôi biết, đã được thiết kế từ lâu trước khi con trỏ thông minh có thể sử dụng được viết.

Đối với Boost, tôi hoàn toàn mong đợi rằng họ sử dụng con trỏ thông minh bất cứ khi nào thích hợp. Bạn có thể phải cụ thể hơn.

Ngoài ra, đừng quên rằng con trỏ thông minh tồn tại để thực thi quyền sở hữu. Nếu API không có ngữ nghĩa sở hữu, thì tại sao lại sử dụng con trỏ thông minh?


19
Qt đã được viết trước khi nhiều chức năng đủ phổ biến trên các nền tảng mà nó muốn sử dụng. Nó đã có con trỏ thông minh trong một thời gian dài và sử dụng chúng để thực hiện việc chia sẻ tài nguyên ngầm trong hầu hết các lớp Q *.
rubenvb

6
Mỗi thư viện GUI không cần phải khôi phục bánh xe. Ngay cả các chuỗi, Qt có QString, wxWidgets cũng có wxString, MFC có tên khủng khiếp CString. UTF-8 std::stringkhông đủ tốt cho 99% nhiệm vụ GUI?
Nghịch đảo

10
@Inverse QString được tạo khi std :: string không xuất hiện.
MrFox

Kiểm tra khi qt được tạo và những gì con trỏ thông minh có sẵn tại thời điểm đó.
Dainius

3

Câu hỏi hay. Tôi không biết các bài viết cụ thể mà bạn giới thiệu, nhưng thỉnh thoảng tôi cũng đọc những điều tương tự. Sự nghi ngờ của tôi là các tác giả của những bài báo như vậy có xu hướng chứa chấp sự thiên vị đối với lập trình theo phong cách C ++. Nếu người viết chỉ lập trình trong C ++ khi anh ta cần, sau đó quay lại Java hoặc sớm nhất có thể, thì anh ta không thực sự chia sẻ suy nghĩ về C ++.

Một người nghi ngờ rằng một số hoặc hầu hết các nhà văn tương tự thích quản lý bộ nhớ thu gom rác. Tôi không, nhưng tôi nghĩ khác với họ.

Con trỏ thông minh là tuyệt vời, nhưng họ phải giữ số lượng tham chiếu. Việc giữ số lượng tham chiếu chịu chi phí - thường là chi phí khiêm tốn, nhưng dù sao chi phí - trong thời gian chạy. Không có gì sai khi tiết kiệm các chi phí này bằng cách sử dụng các con trỏ trần, đặc biệt nếu các con trỏ được quản lý bởi các hàm hủy.

Một trong những điều tuyệt vời về C ++ là hỗ trợ lập trình hệ thống nhúng. Việc sử dụng con trỏ trần là một phần của điều đó.

Cập nhật: Một người bình luận đã quan sát chính xác rằng C ++ mới unique_ptr(có sẵn từ TR1) không tính các tài liệu tham khảo. Bình luận viên cũng có một định nghĩa khác về "con trỏ thông minh" so với tôi nghĩ. Anh ta có thể đúng về định nghĩa.

Cập nhật thêm: Chủ đề bình luận dưới đây đang chiếu sáng. Tất cả được khuyến khích đọc.


2
Để bắt đầu, lập trình hệ thống nhúng là một thiểu số lớn trong tất cả các chương trình, và khá không liên quan. C ++ là một ngôn ngữ có mục đích chung. Thứ hai, shared_ptrgiữ một số lượng tham khảo. Có nhiều loại con trỏ thông minh khác không giữ số tham chiếu. Cuối cùng, các thư viện được đề cập được nhắm mục tiêu vào các nền tảng có nhiều tài nguyên dự phòng. Không phải tôi là người downvoter, nhưng tất cả những gì tôi nói là bài viết của bạn đầy sai.
Cún con

2
@thb - Tôi đồng ý với bạn tình cảm. DeadMG - Hãy cố gắng sống mà không có hệ thống nhúng. Có - một số con trỏ thông minh không có phí, nhưng một số thì có. OP đề cập đến các thư viện. Ví dụ, Boost có các phần được sử dụng bởi các hệ thống nhúng - nhưng con trỏ thông minh có thể không phù hợp cho một số ứng dụng nhất định.
Ed Heal

2
@EdHeal: Không sống mà không có hệ thống nhúng! = Lập trình cho chúng không phải là thiểu số, không liên quan, thiểu số. Con trỏ thông minh phù hợp với mọi tình huống mà bạn cần quản lý vòng đời của tài nguyên.
Cún con

4
shared_ptrkhông có phí. Nó chỉ có chi phí hoạt động nếu bạn không cần ngữ nghĩa sở hữu chung an toàn luồng, đó là những gì nó cung cấp.
R. Martinho Fernandes

1
Không, shared_ptr có chi phí đáng kể so với mức tối thiểu cần thiết cho ngữ nghĩa sở hữu chung an toàn luồng; cụ thể, nó phân bổ một khối heap tách biệt với đối tượng thực tế mà bạn đang chia sẻ, với mục đích duy nhất là lưu trữ số tiền hoàn trả. intrusive_ptr hiệu quả hơn, nhưng (như shared_ptr) nó cũng giả định rằng mọi con trỏ tới đối tượng sẽ là một intrusive_ptr. Bạn có thể nhận được chi phí thấp hơn so với intrusive_ptr với một con trỏ chia sẻ đếm tùy chỉnh, như tôi làm trong ứng dụng của mình, sau đó sử dụng T * bất cứ khi nào bạn có thể đảm bảo rằng ít nhất một con trỏ thông minh sẽ tồn tại lâu hơn giá trị T *.
Qwertie

2

Ngoài ra còn có các loại con trỏ thông minh khác. Bạn có thể muốn một con trỏ thông minh chuyên dụng cho một cái gì đó như sao chép mạng (một con trỏ phát hiện nếu nó được truy cập và gửi bất kỳ sửa đổi nào đến máy chủ hoặc một số thứ khác), lưu giữ lịch sử thay đổi, đánh dấu sự thật rằng nó đã được truy cập để có thể điều tra khi bạn lưu dữ liệu vào đĩa và cứ thế. Không chắc chắn nếu làm điều đó trong con trỏ là giải pháp tốt nhất nhưng sử dụng các loại con trỏ thông minh tích hợp trong thư viện có thể dẫn đến việc mọi người bị khóa vào chúng và mất tính linh hoạt.

Mọi người có thể có tất cả các loại yêu cầu và giải pháp quản lý bộ nhớ khác nhau ngoài con trỏ thông minh. Tôi có thể muốn tự mình quản lý bộ nhớ, tôi có thể phân bổ không gian cho mọi thứ trong nhóm bộ nhớ để nó được phân bổ trước và không phải trong thời gian chạy (hữu ích cho các trò chơi). Tôi có thể đang sử dụng một triển khai rác được thu thập của C ++ (C ++ 11 làm cho điều này có thể thực hiện được mặc dù chưa tồn tại). Hoặc có lẽ tôi chỉ không làm bất cứ điều gì đủ tiến bộ để lo lắng về việc làm phiền họ, tôi có thể biết rằng tôi sẽ không quên các đối tượng chưa được khởi tạo và vân vân. Có lẽ tôi chỉ tự tin vào khả năng quản lý bộ nhớ của mình mà không cần dùng nạng con trỏ.

Tích hợp với C cũng là một vấn đề khác.

Một vấn đề khác là con trỏ thông minh là một phần của STL. C ++ được thiết kế để có thể sử dụng mà không cần STL.


" Một vấn đề khác là con trỏ thông minh là một phần của STL. " Chúng không phải là.
tò mò

0

Nó cũng phụ thuộc vào lĩnh vực bạn làm việc. Tôi viết công cụ trò chơi để kiếm sống, chúng tôi tránh tăng cường như bệnh dịch hạch, trong các trò chơi, việc tăng tốc không thể chấp nhận được. Trong công cụ cốt lõi của chúng tôi, cuối cùng chúng tôi đã viết phiên bản stl của riêng mình (Giống như phiên bản điện tử).

Nếu tôi đã viết một ứng dụng biểu mẫu, tôi có thể xem xét sử dụng con trỏ thông minh; nhưng một khi quản lý bộ nhớ là bản chất thứ hai, việc không kiểm soát chi tiết bộ nhớ sẽ trở nên khó chịu.


3
Không có thứ gọi là "chi phí tăng".
tò mò

4
Tôi chưa bao giờ chia sẻ_ptr làm chậm công cụ trò chơi của tôi đến bất kỳ mức độ đáng chú ý nào. Họ đã đẩy nhanh quá trình sản xuất và gỡ lỗi. Ngoài ra, chính xác những gì bạn có nghĩa là "chi phí tăng?" Đó là một cái chăn khá lớn để đúc.
derpface

@cquilguy: Đây là phần tổng hợp của tất cả các tiêu đề và macro + mẫu voodoo ...
einpoklum
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.