CFSDN nhấn mạnh vào giá trị tạo ra nguồn mở và chúng tôi cam kết xây dựng nền tảng chia sẻ tài nguyên để mọi nhân viên CNTT có thể tìm thấy thế giới tuyệt vời của bạn tại đây.
Bài viết blog CFSDN phân tích mã nguồn C++11shared_ptr và make_shared này được tác giả sưu tầm và biên soạn. Nếu bạn quan tâm đến bài viết này thì nhớ like nhé.
Mục lục
- 0. Lời nói đầu
- 1. Phân tích mã nguồn
- 1.1 Tệp tiêu đề
- 1.2 Cấu trúc
- 1.2.1 Di chuyển hàm tạo của Shared_ptr
- 1.2.2 Sao chép hàm tạo của Shared_ptr
- 1.3 Quá tải bài tập
- 1.4 Giao diện được sửa đổi
- 1.5 Nhận
- 2. làm_chia_sẻ
- 2.1 make_shared Ưu điểm
- 2.1.1 Hiệu quả cao
- 2.1.2 Đặc biệt an toàn
- 2.2 Nhược điểm của make_shared
- 3. Ví dụ
- Tóm tắt
0. Lời nói đầu
Cái gọi là con trỏ thông minh có thể hiểu theo nghĩa đen là con trỏ "thông minh". Cụ thể, cách sử dụng con trỏ thông minh cũng tương tự như con trỏ thông thường. Điểm khác biệt là con trỏ thông minh có thể tự động giải phóng bộ nhớ được phân bổ vào thời điểm thích hợp. Nói cách khác, sử dụng con trỏ thông minh có thể tránh được vấn đề "quên giải phóng bộ nhớ và gây rò rỉ bộ nhớ" một cách hiệu quả. Có thể thấy, C++ đã dần bắt đầu hỗ trợ cơ chế thu gom rác, mặc dù mức độ hỗ trợ hiện tại vẫn còn hạn chế.
Shared_ptr, Unique_ptr vàweak_ptr đã được phát hành trong c++11 để quản lý tài nguyên và tất cả chúng đều được xác định trong tệp tiêu đề bộ nhớ.
-
std::shared_ptr
Cho phép nhiều phiên bản Shared_ptr trỏ đến cùng một đối tượng và quản lý chúng thông qua việc đếm;
-
std::unique_ptr
là một đối tượng độc quyền;
-
yếu_ptr
Nó là một lớp phụ trợ, một tham chiếu yếu, trỏ đến đối tượng được quản lý bởi Shared_ptr.
1. Phân tích mã nguồn
1.1 Tệp tiêu đề
1.2 Cấu trúc
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
constexpr shared_ptr() không ngoại lệ;
bản mẫu
<
lớp học
Vâng>
rõ ràng
shared_ptr(Y* p);
bản mẫu
<
lớp học
VÀ,
lớp học
D> shared_ptr(Y* p, D d);
bản mẫu
<
lớp học
VÀ,
lớp học
D,
lớp học
A> shared_ptr(Y* p, D d, A a);
bản mẫu
<
lớp học
D> shared_ptr(nullptr_t p, D d);
bản mẫu
<
lớp học
D,
lớp học
A> shared_ptr(nullptr_t p, D d, A a);
bản mẫu
<
lớp học
Y> shared_ptr(
hằng số
shared_ptr& r, T *p) không ngoại lệ;
chia sẻ_ptr(
hằng số
shared_ptr& r) noexcept;
bản mẫu
<
lớp học
Y> shared_ptr(
hằng số
shared_ptr& r) không ngoại lệ;
shared_ptr(shared_ptr&& r) không ngoại lệ;
bản mẫu
<
lớp học
Y> shared_ptr(shared_ptr&& r) không ngoại lệ;
bản mẫu
<
lớp học
Vâng>
rõ ràng
chia sẻ_ptr(
hằng số
yếu_ptr& r);
bản mẫu
<
lớp học
Y> Shared_ptr(auto_ptr&& r);
bản mẫu
<
lớp học
VÀ,
lớp học
D> shared_ptr(unique_ptr&& r);
shared_ptr(nullptr_t): shared_ptr() { }
|
Có khá nhiều hàm tạo. Hãy lấy một cái và xem mã nguồn.
1.2.1 Di chuyển hàm tạo của Shared_ptr
?
1
2
3
4
5
6
7
8
9
|
bản mẫu
<
lớp học
_Tp>
nội tuyến
shared_ptr<_Tp>::shared_ptr(shared_ptr&& __r) _KHÔNG NGOẠI TRỪ
: __ptr_(__r.__ptr_),
__cntrl_(__r.__cntrl_)
{
__r.__ptr_ = 0;
__r.__cntrl_ = 0;
}
|
Như bạn có thể biết, Shared_ptr lưu trữ một con trỏ đối tượng __ptr_ và __cntrl_ được sử dụng để đếm. Hai biến này là các biến thành viên riêng của Shared_ptr:
?
1
2
3
4
5
6
7
8
9
10
|
bản mẫu
<
lớp học
_Tp>
lớp học
chia sẻ_ptr {
định nghĩa kiểu
_Tp loại phần tử;
riêng tư
:
loại_phần_tử* __ptr_;
__shared_weak_count* __cntrl_;
...
}
|
Ngoài ra, vì hàm tạo di chuyển chỉ là di chuyển nên nó chỉ chuyển Shared_ptr cũ sang cái mới.
1.2.2 Sao chép hàm tạo của Shared_ptr
?
1
2
3
4
5
6
7
8
9
|
bản mẫu
<
lớp học
_Tp>
nội tuyến
shared_ptr<_Tp>::shared_ptr(
hằng số
shared_ptr& __r) _KHÔNG NGOẠI TRỪ
: __ptr_(__r.__ptr_),
__cntrl_(__r.__cntrl_)
{
nếu như
(__cntrl_)
__cntrl_->__add_shared();
}
|
Tương tự như cách xây dựng di chuyển, phiên bản Shared_ptr, cần lấy __ptr_ và __cntrl từ các tham số.
Tuy nhiên, không giống như hàm tạo di chuyển, số lượng đối tượng được tăng lên trong quá trình xây dựng bản sao.
Sau đây là ví dụ về cách chia sẻ_ptr thường được tạo:
?
1
2
3
4
5
|
std::shared_ptr p1 // Không truyền bất kỳ tham số thực tế nào
std::shared_ptr p2(nullptr); // Truyền vào con trỏ null nullptr
std::shared_ptr p3(new int(10)); // Chỉ định con trỏ làm tham số
std::shared_ptr p4(p3); //Hoặc std::shared_ptr p4 = p3;
std::shared_ptr p5(std::move(p4)); //Hoặc sử dụng std::shared_ptr p5 = std::move(p4);
|
1.3 Quá tải bài tập
?
1
2
3
4
5
6
|
toán tử shared_ptr&=(
hằng số
shared_ptr& r) noexcept;
bản mẫu
<
lớp học
Y> shared_ptr& toán tử=(
hằng số
shared_ptr& r) không ngoại lệ;
toán tử shared_ptr&=(shared_ptr&& r) không ngoại lệ;
bản mẫu
<
lớp học
Y> shared_ptr& toán tử=(shared_ptr&& r);
bản mẫu
<
lớp học
Y> shared_ptr& toán tử=(auto_ptr&& r);
bản mẫu
<
lớp học
VÀ,
lớp học
D> shared_ptr& toán tử=(unique_ptr&& r);
|
1.4 Giao diện được sửa đổi
?
1
2
3
4
5
|
vô hiệu
trao đổi(shared_ptr& r) không ngoại lệ;
vô hiệu
reset() không ngoại trừ;
bản mẫu
<
lớp học
Vâng>
vô hiệu
đặt lại(Y* p);
bản mẫu
<
lớp học
VÀ,
lớp học
D>
vô hiệu
thiết lập lại(Y* p, D d);
bản mẫu
<
lớp học
VÀ,
lớp học
D,
lớp học
A>
vô hiệu
thiết lập lại(Y* p, D d, A a);
|
reset về cơ bản là một cấu trúc tương ứng.
1.5 Nhận
?
1
2
3
4
5
6
|
T* lấy()
hằng số
không ngoại trừ;
Toán tử T&*()
hằng số
không ngoại trừ;
Toán tử T*->()
hằng số
không ngoại trừ;
dài
sử dụng_số lượng()
hằng số
không ngoại trừ;
bool
độc nhất()
hằng số
không ngoại trừ;
rõ ràng
người điều hành
bool
()
hằng số
không ngoại trừ;
|
Các hàm thành viên của Shared_ptr được tóm tắt như sau:
Tên phương thức thành viên. |
Chức năng. |
toán tử=() 。 |
Quá tải số gán để các con trỏ thông minh Shared_ptr cùng loại có thể gán giá trị cho nhau. |
toán tử*() 。 |
Nạp chồng số * để lấy dữ liệu được trỏ đến bởi đối tượng con trỏ thông minh Shared_ptr hiện tại. |
toán tử->() 。 |
Toán tử -> bị quá tải, khi kiểu dữ liệu được trỏ đến bởi con trỏ thông minh là cấu trúc tùy chỉnh, thì có thể lấy được các thành viên nội bộ được chỉ định thông qua toán tử ->. |
tráo đổi() . |
Hoán đổi nội dung của 2 con trỏ thông minh Shared_ptr cùng loại. |
cài lại() . |
Khi hàm không có tham số thực tế, hàm sẽ giảm số tham chiếu của bộ nhớ heap được trỏ tới bởi Shared_ptr hiện tại xuống 1 và đặt lại đối tượng hiện tại thành con trỏ rỗng khi bộ nhớ heap mới được yêu cầu được truyền tới hàm, hàm được gọi Đối tượng Shared_ptr sẽ có quyền sở hữu không gian lưu trữ và giá trị ban đầu của số tham chiếu là 1. |
lấy() . |
Lấy con trỏ thông thường chứa bên trong đối tượng Shared_ptr. |
sử dụng_đếm() 。 |
Trả về số lượng tất cả các đối tượng Shared_ptr trỏ đến cùng một điểm với đối tượng Shared_ptr hiện tại (bao gồm cả đối tượng này). |
độc nhất() . |
Xác định xem bộ nhớ heap được trỏ đến bởi đối tượng Shared_ptr hiện tại có còn được các đối tượng Shared_ptr khác trỏ tới hay không. |
toán tử bool() 。 |
Xác định xem đối tượng Shared_ptr hiện tại có phải là con trỏ thông minh null hay không. Nếu đó là con trỏ null, hãy trả về false nếu không, hãy trả về true. |
Ngoài các hàm thành viên trên, tiêu chuẩn C++ 11 còn hỗ trợ các thao tác ==, != và >= giữa các đối tượng Shared_ptr cùng loại hoặc giữa Shared_ptr và nullptr.
Để ý:
- Shared_ptr được sử dụng để chia sẻ quyền sở hữu một đối tượng và xác nhận việc tái chế tự động thông qua việc đếm;
- Shared_ptr chia sẻ quyền sở hữu đối tượng, bằng cách lưu trữ con trỏ của đối tượng và lấy con trỏ của đối tượng được lưu trữ thông qua get();
- Các phiên bản Shared_ptr khác nhau có thể được sử dụng trong nhiều luồng cùng lúc và các phiên bản này có thể trỏ đến cùng một đối tượng. Khi nhiều luồng truy cập vào cùng một phiên bản Shared_ptr mà không đồng bộ hóa và gọi các hàm thành viên không phải const của nó, có thể xảy ra xung đột dữ liệu. Cuộc đua dữ liệu có thể được bảo vệ bằng cách sử dụng các hàm std::atomic*;
2. làm_chia_sẻ
?
1
2
|
bản mẫu
<
lớp học
T,
lớp học
... Đối số>
shared_ptr make_shared(Args&&... args);
|
C++11 cũng cung cấp hàm bên ngoài make_shared cho Shared_ptr, được sử dụng để tạo một phiên bản Shared_ptr.
C++ khuyên bạn nên sử dụng make_shared để tạo phiên bản Shared_ptr bất cứ khi nào có thể.
Phần sau đây giải thích những ưu điểm và nhược điểm của make_shared.
2.1 make_shared Ưu điểm
2.1.1 Hiệu quả cao
Shared_ptr cần duy trì thông tin đếm tham chiếu:
- Tham chiếu mạnh được sử dụng để ghi lại số lượng shared_ptrs còn sống hiện đang giữ đối tượng. Đối tượng được chia sẻ sẽ bị hủy (cũng có thể được giải phóng) khi tham chiếu mạnh cuối cùng rời đi.
- Tham chiếu yếu được sử dụng để ghi lại số lượng tham chiếu yếu hiện đang quan sát đối tượng. Khi tham chiếu yếu cuối cùng rời đi, khối kiểm soát thông tin nội bộ được chia sẻ sẽ bị hủy và giải phóng (đối tượng dùng chung cũng sẽ được giải phóng, nếu nó chưa được giải phóng. ).
Nếu bạn phân bổ đối tượng bằng cách sử dụng một biểu thức thô mới và sau đó chuyển nó tới Shared_ptr (nghĩa là sử dụng hàm tạo Shared_ptr), thì việc triển khai Shared_ptr không có lựa chọn nào khác ngoài việc phân bổ khối điều khiển riêng biệt:
?
1
2
|
xe p =
mới
tiện ích();
shared_ptr sp1{ p }, sp2{ sp1 };
|

Nếu bạn chọn sử dụng make_shared thì tình huống sẽ như sau
?
1
|
tự động sp1 = make_shared(), sp2{ sp1 };
|

Hành động cấp phát bộ nhớ có thể được hoàn thành trong một lần. Điều này giúp giảm số lượng cấp phát bộ nhớ, vốn là những hoạt động tốn kém.
2.1.2 Đặc biệt an toàn
?
1
2
3
|
vô hiệu
Đ(
hằng số
std::shared_ptr& Trái,
hằng số
std::shared_ptr& rhs) {
}
F(std::shared_ptr(
mới
Trái(
"đồ ngốc"
)),
std::shared_ptr(
mới
Rh(
"thanh"
)));
|
C++ không đảm bảo thứ tự đánh giá tham số và thứ tự đánh giá các biểu thức bên trong, do đó thứ tự thực hiện có thể như sau
- mới Lhs(“foo”))
- Rhs(“bar”) mới)
- std::shared_ptr
- std::shared_ptr
Được rồi, bây giờ hãy giả sử rằng ở bước 2, một ngoại lệ được đưa ra (chẳng hạn như hết bộ nhớ, nói tóm lại là hàm tạo của Rhs là bất thường), thì bộ nhớ của đối tượng Lhs được áp dụng trong bước đầu tiên bị rò rỉ. về vấn đề này Vấn đề là ở chỗ Shared_ptr không nhận được con trỏ thô ngay lập tức.
Chúng ta có thể khắc phục vấn đề này như sau.
?
1
2
3
|
tự động lhs = std::shared_ptr(
mới
Trái(
"đồ ngốc"
));
tự động rhs = std::shared_ptr(
mới
Rh(
"thanh"
));
F(trái, phải);
|
Tất nhiên, cách tiếp cận được khuyến nghị là sử dụng std::make_shared thay thế:
?
1
|
F(std::make_shared(
"đồ ngốc"
), std::make_shared(
"thanh"
));
|
2.2 Nhược điểm của make_shared
make_shared không thể được sử dụng khi hàm tạo được bảo vệ hoặc riêng tư.
Mặc dù make_shared tốt nhưng nó cũng có một số vấn đề. Ví dụ, khi đối tượng tôi muốn tạo không có hàm tạo công khai thì không thể sử dụng make_shared. Tất nhiên, chúng ta có thể sử dụng một số thủ thuật để giải quyết vấn đề này, chẳng hạn như Làm thế nào để giải quyết vấn đề này. Tôi gọi đây ::std::make_shared trên một lớp chỉ có các hàm tạo được bảo vệ hoặc riêng tư?
Bộ nhớ của đối tượng có thể không được lấy lại kịp thời.
make_shared chỉ phân bổ bộ nhớ một lần, điều này có vẻ ổn. bộ nhớ sẽ chỉ được giải phóng khi yếu_ptr cuối cùng rời khỏi phạm vi Bộ nhớ ban đầu được giải phóng khi tham chiếu mạnh giảm xuống 0 giờ đã trở thành tham chiếu mạnh và chỉ có thể được giải phóng khi tham chiếu giảm xuống 0. Làm chậm thời gian giải phóng bộ nhớ một cách bất ngờ. Đây là một vấn đề cần được chú ý trong các tình huống có yêu cầu bộ nhớ cao.
Không có cách nào để thêm một trình xóa như hàm tạo của Shared_ptr.
3. Ví dụ
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include
#include
sử dụng
không gian tên
tiêu chuẩn;
số nguyên
chủ yếu()
{
std::shared_ptr<
số nguyên
> trang1(
mới
số nguyên
(10));
std::shared_ptr<
số nguyên
> p2(p1);
lệnh << *p2 << kết thúc;
p1.reset();
nếu như
(p1) {
đếm <<
"p1 không trống"
<< kết thúc;
}
khác
{
đếm <<
"p1 trống"
<< kết thúc;
}
lệnh << *p2 << kết thúc;
cout << p2.use_count() << endl;
trở lại
0;
}
|
Kết quả chạy:
10 p1 trống 10 1 .
thẩm quyền giải quyết:
C++11 std::shared_ptr tóm tắt và giải thích chi tiết về mã ví dụ sử dụng.
Tóm tắt
Bài viết này kết thúc tại đây, tôi hy vọng nó có thể hữu ích cho bạn và tôi hy vọng bạn có thể chú ý hơn đến nội dung của tôi! .
Liên kết gốc: https://blog.csdn.net/shift_wwx/article/details/120336234.
Cuối cùng, bài viết phân tích chi tiết về mã nguồn C++11shared_ptr và make_shared kết thúc tại đây. Nếu bạn muốn biết thêm về phân tích chi tiết về mã nguồn C++11shared_ptr và make_shared, vui lòng tìm kiếm các bài viết CFSDN hoặc tiếp tục. duyệt các bài viết liên quan, tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!