Tại sao các ví dụ và hướng dẫn Qt chính thức không sử dụng con trỏ thông minh?


76

Tại sao các ví dụ và hướng dẫn chính thức về thư viện Qt không bao giờ sử dụng con trỏ thông minh? Tôi chỉ xem newdeleteđể tạo và phá hủy các widget.

Tôi đã tìm kiếm cơ sở lý luận nhưng không thể tìm thấy và bản thân tôi cũng không thấy lý do nào ngoại trừ nếu lý do lịch sử hoặc khả năng tương thích ngược: không phải ai cũng muốn chương trình kết thúc nếu một hàm tạo widget bị lỗi và xử lý nó thông qua try / catch khối chỉ là xấu (ngay cả khi được sử dụng ở một số nơi). Việc các widget dành cho cha mẹ có thể thuộc quyền sở hữu của con cái cũng chỉ giải thích một phần cho tôi, vì bạn vẫn phải sử dụng deletecho cha mẹ ở một mức độ nào đó.


9
Qt có nhiều con trỏ thông minh trước c ++ hiện đại. Và chúng được sử dụng trong một số dự án. Bạn không thấy trong các ví dụ đó vì QObject xử lý tất cả các vấn đề liên quan đến trẻ em / cha mẹ và ở đó vòng đời. để biết thêm thông tin về con trỏ thông minh Qt, hãy truy cập trang này -> wiki.qt.io/Smart_Pointers
sanjay

2
"bạn sẽ vẫn phải sử dụng xóa cho cha mẹ ở một số cấp độ" - không nhất thiết, vì đối tượng gốc thường nằm trong main()đó nên nó sẽ được tự động thu thập.
dtech

Có vẻ như QPointer có thể được sử dụng một cách rõ ràng trong các ngữ cảnh khác nhau: tức là nó hoạt động giống như một "con trỏ phạm vi" nếu tiện ích con được tham chiếu KHÔNG được thêm vào đối tượng mẹ trước khi QPointer đi ra khỏi phạm vi, nhưng mặt khác, vẫn bảo toàn tiện ích con cuộc sống nếu đã được thêm vào một parent.before các QPointer đi ra khỏi phạm vi
Martin

Nó không giống như những con trỏ đơn giản không còn được dùng nữa hay gì đó, chúng vẫn hữu ích như chúng đã từng. Trái ngược với những gì một số người có thể nói, bạn vẫn hoàn toàn có thể sử dụng chúng, và trong trường hợp Qt, có rất nhiều biện pháp bảo vệ xung quanh "các góc nhọn" của con trỏ đơn giản. Nhưng vâng, chúng ta hãy ngừng sử dụng con trỏ ngay bây giờ vì C ++ cuối cùng đã quyết định kết hợp con trỏ thông minh.
dtech

5
@ddriver, điều đáng nói là lời khuyên của chuyên gia không phải là "không bao giờ sử dụng con trỏ thô". Lời khuyên là "tránh sở hữu những con trỏ thô". Đó là một sự khác biệt quan trọng mà bạn dường như không thực hiện được.
Tim Seguine

Câu trả lời:


67

Vì Qt dựa vào mô hình cha-con để quản lý tài nguyên Qobject. Nó tuân theo mô hình hỗn hợp + Chuỗi trách nhiệm, được sử dụng từ quản lý sự kiện đến quản lý bộ nhớ, vẽ, xử lý tệp, v.v.

Trên thực tế, việc cố gắng sử dụng QObject trong con trỏ được chia sẻ \ duy nhất đang sử dụng quá mức (99% thời gian).

  1. Bạn phải cung cấp một trình xóa tùy chỉnh sẽ gọi deleteLater
  2. Qobject của bạn với cha mẹ đã có một tham chiếu trong đối tượng cha. Vì vậy, bạn biết rằng một đối tượng không bị rò rỉ miễn là đối tượng còn tồn tại. Khi nào cần tẩy có thể gọi điện deleteLatertrực tiếp.
  3. QWidget của bạn không có cha mẹ đã có một tham chiếu trong đối tượng Qapplication . Vì vậy, tương tự như điểm 2.

Điều đó nói rằng, bạn vẫn có thể sử dụng RAII với Qt. Ví dụ, QPointer hoạt động như một tham chiếu yếu trên a QObject. Tôi muốn sử dụng QPointer<QWidget>hơn là QWidget*.

lưu ý: để không nghe quá fanboy, hai từ: Qt + valgrind.


2
điểm 4. nếu cha mẹ bị xóa thì shared_ptr và unique_ptr sẽ có một con trỏ treo. Điều này được giải quyết bởi QPointer sẽ được xóa tự động bởi QObject trỏ tới.
ratchet freak

Đối với các đối tượng không phải widget không có cha mẹ, có cần thiết phải gọi deleteLaterthay vì chỉ deletenhập đối tượng không? Tôi thường quản lý các phiên bản của các lớp có nguồn gốc từ QObject bằng cách sử dụng con trỏ thông minh, vì vậy tôi đã tìm kiếm câu trả lời cho điều này trước đây chỉ để đảm bảo rằng tôi không làm điều gì đó nguy hiểm và theo như tôi có thể nói thì tài liệu không chỉ ra rằng nó sai đến deletemột con trỏ QObject thường (ngay cả khi nó được sở hữu bởi một đối tượng phụ huynh, vì destructor sẽ loại bỏ tham khảo của mẹ).
Kyle Strand

2
tài liệu cảnh báo bạn chống lại nó . trực tiếp gọi xóa có thể gây ra sự cố do các sự kiện đang chờ xử lý \ các kết nối được xếp hàng đợi đến đối tượng (hoặc con của nó).
UmNyobe

@UmNyobe Đúng vậy, nếu bạn xóa một QObjecttừ khác QThread. Bộ hủy QObjectngắt kết nối tất cả các tín hiệu đến và đi và tự động xóa các sự kiện đang chờ xử lý khỏi hàng đợi sự kiện của nó. Do đó, việc gọi deletea QObjectlà ok, nếu bạn thực hiện từ bên trong chuỗi của nó. Nói như vậy, bạn khó có thể làm gì sai khi sử dụng deleteLater()ngoại trừ trường hợp bạn cần dọn dẹp ngay lập tức vì lý do nào đó.
Ralph Tandetzky,

@UmNyobe Theo tài liệu QPointer, nó không liên quan gì đến RAII. Nó chỉ đảm bảo rằng con trỏ sẽ bằng 0 nếu ai đó khác xóa QObject mà nó trỏ tới. Nhưng nó không bao giờ xóa chính QObject. Nếu bạn RAII, QSharedPointer nên là phương pháp ưu tiên.
Fritz

26

Con trỏ thông minh cho trẻ em

Các lớp con trỏ thông minh std::unique_ptrstd::shared_ptrdành cho quản lý bộ nhớ. Có một con trỏ thông minh như vậy có nghĩa là bạn sở hữu con trỏ. Tuy nhiên, khi tạo một QObjecthoặc một kiểu dẫn xuất với QObjectcha mẹ, quyền sở hữu (trách nhiệm dọn dẹp) sẽ được chuyển giao cho cha mẹ QObject. Trong trường hợp đó, các con trỏ thông minh của thư viện tiêu chuẩn là không cần thiết, hoặc thậm chí nguy hiểm, vì chúng có thể gây ra việc xóa hai lần. Rất tiếc!

Con trỏ thô đến trẻ mồ côi

Tuy nhiên, khi một QObject(hoặc kiểu dẫn xuất) được tạo trên heap mà không có cha mẹ thì QObjectmọi thứ sẽ rất khác. Trong trường hợp đó, bạn không nên chỉ giữ một con trỏ thô, mà là một con trỏ thông minh, tốt nhất là một con trỏ std::unique_ptrtới đối tượng. Bằng cách đó, bạn có được sự an toàn về tài nguyên. Nếu sau này bạn chuyển quyền sở hữu đối tượng cho cha mẹ, QObjectbạn có thể sử dụng std::unique_ptr<T>::release(), như sau:

auto obj = std::make_unique<MyObject>();
// ... do some stuff that might throw ...
QObject parentObject;
obj->setParent( &parentObject );
obj.release();

Nếu những thứ bạn làm trước khi trao cho đứa trẻ mồ côi cha mẹ của bạn ném ra một ngoại lệ, thì bạn sẽ bị rò rỉ bộ nhớ, nếu bạn sử dụng con trỏ thô để giữ đối tượng. Nhưng đoạn mã trên được lưu lại để chống lại sự rò rỉ như vậy.

Trên một lưu ý chung hơn

Lời khuyên C ++ hiện đại không phải là tránh tất cả các con trỏ thô cùng nhau, mà là tránh sở hữu các con trỏ thô. Tôi có thể thêm một lời khuyên C ++ hiện đại khác: Không sử dụng con trỏ thông minh cho các đối tượng thuộc sở hữu của một số thực thể chương trình khác.


4
Đúng. Điều này. Quyền sở hữu là tất cả. Tôi ước tài liệu Qt rõ ràng hơn một chút về điều này; dường như có nhiều tham chiếu đến việc trẻ em bị xóa "tự động", nhưng không có nhiều thông tin về cách xử lý trẻ mồ côi.
Kyle Strand

+1, đặc biệt cho đoạn cuối cùng. Tôi đã chứng kiến ​​rất nhiều trường hợp mọi người không ngừng cố gắng sử dụng con trỏ thông minh bất cứ khi nào họ có thể (đặc biệt đối với truyền tham số, hãy xem thêm hướng dẫn cốt lõi này .
andreee

13

Bạn đã trả lời câu hỏi của riêng bạn: except if it's for historic reasons/backward compatibility. Một thư viện khổng lồ như QT không thể cho rằng mọi người sử dụng thư viện đều có trình biên dịch hỗ trợ C ++ 11. newdeleteđược đảm bảo tồn tại trong các tiêu chuẩn trước đó.

Tuy nhiên, nếu bạn được hỗ trợ để sử dụng con trỏ thông minh, tôi sẽ khuyến khích sử dụng chúng thay cho con trỏ thô.


tuy nhiên con trỏ thông minh đã tồn tại trước C ++ 11 và có thể trước cả QT. Đó có phải là một quyết định thiết kế tồi ngay từ đầu?
Martin

21
err ... khi qt và một số thư viện khác được thiết kế bằng c ++ thậm chí không có lớp chuỗi tiêu chuẩn.
UmNyobe

Nếu con trỏ thông minh trước C ++ 11 có nghĩa là auto_ptr, thì đó là quyết định đúng đắn khi không sử dụng nó. Không có con trỏ thông minh được tiêu chuẩn hóa tốt trước C ++ 11.
Zyx 2000

2
Không có cái nào được tiêu chuẩn hóa tốt, nhưng một số cái dựa trên Qt, ví dụ QSharedPointerQScopedPointer.
Ruslan

Câu trả lời này là ngày vì Qt hiện đại yêu cầu hỗ trợ C ++ 11.
Mikhail

10

Ngoài những gì @Jamey đã nói:

Nếu bạn thiết kế nó một cách khéo léo, bạn có thể không bao giờ phải sử dụng một tính năng xóa trên một widget. Giả sử bạn có một cửa sổ chính và bạn đang tạo một đối tượng tự động của nó và chạy cửa sổ đó trong vòng lặp sự kiện. Bây giờ phần còn lại tất cả các mục trong tiện ích con này có thể được thêm vào làm con của nó. Và vì bạn đang thêm chúng vào MainWindow này một cách trực tiếp / gián tiếp khi còn nhỏ, nên khi bạn đóng cửa sổ chính này, mọi thứ sẽ được tự động xử lý. Chỉ cần bạn phải đảm bảo rằng tất cả các đối tượng / widget động mà bạn đã tạo là con / cháu của MainWindow. Do đó không cần xóa rõ ràng ..


2
Bạn sẽ phải thêm từng đứa trẻ vào cửa sổ chính ngay sau khi bạn tạo nó hoặc ít nhất là trước bất kỳ thứ gì có thể ném hoặc thứ đó không hoạt động, một hạn chế mà tôi muốn tránh để ghi nhớ
Martin

2
@ Martin gì ném trong Qt ứng dụng thông thường (trừ trường hợp một là một fan hâm mộ thực sự, đó là), do đó, đến trên :)
mlvljr

5
  • QObject có cha mẹ được định nghĩa và cấu trúc dạng cây của chương trình cho phép quản lý bộ nhớ khá hiệu quả.

  • Tính năng động trong Qt phá vỡ lý tưởng tốt đẹp đó, ví dụ như truyền một con trỏ thô xung quanh. Người ta có thể dễ dàng nắm giữ một dangling pointer, nhưng đó là một vấn đề phổ biến trong lập trình.

  • Con trỏ thông minh Qt, thực sự là một tham chiếu yếu, QPointer<T>
    và cung cấp một số kẹo STL.

  • Người ta cũng có thể trộn với std::unique_ptr và những thứ tương tự, nhưng nó chỉ nên được sử dụng cho máy móc không phải Qt trong chương trình của bạn.

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.