Tôi bắt đầu nghiên cứu các con trỏ thông minh của C ++ 11 và tôi không thấy bất kỳ cách sử dụng hữu ích nào std::weak_ptr
. Ai đó có thể cho tôi biết khi nào std::weak_ptr
là hữu ích / cần thiết?
Tôi bắt đầu nghiên cứu các con trỏ thông minh của C ++ 11 và tôi không thấy bất kỳ cách sử dụng hữu ích nào std::weak_ptr
. Ai đó có thể cho tôi biết khi nào std::weak_ptr
là hữu ích / cần thiết?
Câu trả lời:
Một ví dụ tốt sẽ là một bộ đệm.
Đối với các đối tượng được truy cập gần đây, bạn muốn giữ chúng trong bộ nhớ, vì vậy bạn giữ một con trỏ mạnh cho chúng. Định kỳ, bạn quét bộ đệm và quyết định những đối tượng nào không được truy cập gần đây. Bạn không cần phải giữ những thứ đó trong bộ nhớ, vì vậy bạn sẽ thoát khỏi con trỏ mạnh.
Nhưng nếu đối tượng đó đang được sử dụng và một số mã khác giữ một con trỏ mạnh đến nó thì sao? Nếu bộ đệm được loại bỏ con trỏ duy nhất của nó đến đối tượng, nó không bao giờ có thể tìm lại được. Vì vậy, bộ đệm giữ một con trỏ yếu đến các đối tượng mà nó cần tìm nếu chúng tình cờ ở lại trong bộ nhớ.
Đây chính xác là những gì một con trỏ yếu làm - nó cho phép bạn xác định vị trí một đối tượng nếu nó vẫn ở xung quanh, nhưng không giữ nó xung quanh nếu không có gì khác cần nó.
std::weak_ptr
là một cách rất tốt để giải quyết vấn đề con trỏ lơ lửng . Bằng cách chỉ sử dụng các con trỏ thô, không thể biết liệu dữ liệu được tham chiếu đã được xử lý hay chưa. Thay vào đó, bằng cách cho phép std::shared_ptr
quản lý dữ liệu và cung cấp std::weak_ptr
cho người dùng dữ liệu, người dùng có thể kiểm tra tính hợp lệ của dữ liệu bằng cách gọi expired()
hoặc lock()
.
Bạn không thể làm điều này std::shared_ptr
một mình, bởi vì tất cả các std::shared_ptr
trường hợp chia sẻ quyền sở hữu dữ liệu không bị xóa trước khi tất cả các trường hợp std::shared_ptr
bị xóa. Dưới đây là một ví dụ về cách kiểm tra con trỏ lơ lửng bằng cách sử dụng lock()
:
#include <iostream>
#include <memory>
int main()
{
// OLD, problem with dangling pointer
// PROBLEM: ref will point to undefined data!
int* ptr = new int(10);
int* ref = ptr;
delete ptr;
// NEW
// SOLUTION: check expired() or lock() to determine if pointer is valid
// empty definition
std::shared_ptr<int> sptr;
// takes ownership of pointer
sptr.reset(new int);
*sptr = 10;
// get pointer to data without taking ownership
std::weak_ptr<int> weak1 = sptr;
// deletes managed object, acquires new pointer
sptr.reset(new int);
*sptr = 5;
// get pointer to new data without taking ownership
std::weak_ptr<int> weak2 = sptr;
// weak1 is expired!
if(auto tmp = weak1.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak1 is expired\n";
// weak2 points to new data (5)
if(auto tmp = weak2.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak2 is expired\n";
}
std::weak_ptr::lock
tạo ra một cái mới std::shared_ptr
mà chia sẻ quyền sở hữu của đối tượng được quản lý.
Một câu trả lời khác, hy vọng đơn giản hơn. (đối với nhân viên của Google)
Giả sử bạn có Team
và Member
đồ vật.
Rõ ràng đó là một mối quan hệ: Team
đối tượng sẽ có con trỏ đến nó Members
. Và có khả năng các thành viên cũng sẽ có một con trỏ trở lại Team
đối tượng của họ .
Sau đó, bạn có một chu kỳ phụ thuộc. Nếu bạn sử dụng shared_ptr
, các đối tượng sẽ không còn được tự động giải phóng khi bạn từ bỏ tham chiếu trên chúng, bởi vì chúng tham chiếu lẫn nhau theo cách tuần hoàn. Đây là một rò rỉ bộ nhớ.
Bạn phá vỡ điều này bằng cách sử dụng weak_ptr
. "Chủ sở hữu" thường sử dụng shared_ptr
và "chủ sở hữu" sử dụng a weak_ptr
cho cha mẹ của nó và chuyển đổi tạm thời thành shared_ptr
khi nó cần quyền truy cập vào cha mẹ của nó.
Lưu trữ một ptr yếu:
weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared
sau đó sử dụng nó khi cần thiết
shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
// yes, it may fail if the parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
shared_ptr
là chia sẻ quyền sở hữu, vì vậy không ai có trách nhiệm đặc biệt để giải phóng bộ nhớ, nó được giải phóng tự động khi không còn sử dụng. Trừ khi có một vòng lặp ... Bạn có thể có một vài đội chia sẻ cùng một người chơi (các đội trong quá khứ?). Nếu đối tượng nhóm "sở hữu" các thành viên, thì không cần phải sử dụng shared_ptr
để bắt đầu.
shared_ptr
tham chiếu bởi các "thành viên trong nhóm", khi nào nó sẽ bị phá hủy? Những gì bạn đang mô tả là một trường hợp không có vòng lặp.
Đây là một ví dụ, được đưa ra cho tôi bởi @jleahy: Giả sử bạn có một tập hợp các tác vụ, được thực thi không đồng bộ và được quản lý bởi một std::shared_ptr<Task>
. Bạn có thể muốn làm một cái gì đó với các nhiệm vụ đó theo định kỳ, vì vậy một sự kiện hẹn giờ có thể đi qua a std::vector<std::weak_ptr<Task>>
và đưa ra các nhiệm vụ phải làm. Tuy nhiên, đồng thời một nhiệm vụ có thể đã quyết định đồng thời rằng nó không còn cần thiết và chết. Do đó, bộ định thời có thể kiểm tra xem tác vụ có còn tồn tại hay không bằng cách tạo một con trỏ dùng chung từ con trỏ yếu và sử dụng con trỏ dùng chung đó, miễn là nó không rỗng.
Chúng rất hữu ích với Boost.Asio khi bạn không được đảm bảo rằng đối tượng đích vẫn tồn tại khi trình xử lý không đồng bộ được gọi. Bí quyết là liên kết a weak_ptr
vào đối tượng xử lý không đồng bộ, sử dụng std::bind
hoặc chụp lambda.
void MyClass::startTimer()
{
std::weak_ptr<MyClass> weak = shared_from_this();
timer_.async_wait( [weak](const boost::system::error_code& ec)
{
auto self = weak.lock();
if (self)
{
self->handleTimeout();
}
else
{
std::cout << "Target object no longer exists!\n";
}
} );
}
Đây là một biến thể của self = shared_from_this()
thành ngữ thường thấy trong các ví dụ Boost.Asio, trong đó trình xử lý không đồng bộ đang chờ xử lý sẽ không kéo dài thời gian tồn tại của đối tượng đích, nhưng vẫn an toàn nếu đối tượng đích bị xóa.
this
self = shared_from_this()
thành ngữ khi trình xử lý gọi các phương thức trong cùng một lớp.
shared_ptr : giữ đối tượng thực sự.
yếu_ptr : sử dụng lock
để kết nối với chủ sở hữu thực hoặc trả về NULL shared_ptr
nếu không.
Nói một cách đơn giản, weak_ptr
vai trò tương tự như vai trò của cơ quan nhà ở . Không có đại lý, để có được một ngôi nhà cho thuê, chúng tôi có thể phải kiểm tra các ngôi nhà ngẫu nhiên trong thành phố. Các đại lý đảm bảo rằng chúng tôi chỉ ghé thăm những ngôi nhà vẫn có thể truy cập và cho thuê.
weak_ptr
cũng tốt để kiểm tra việc xóa chính xác của một đối tượng - đặc biệt là trong các bài kiểm tra đơn vị. Trường hợp sử dụng điển hình có thể trông như thế này:
std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());
Khi sử dụng con trỏ, điều quan trọng là phải hiểu các loại con trỏ khác nhau có sẵn và khi sử dụng mỗi loại con trỏ có ý nghĩa. Có bốn loại con trỏ trong hai loại như sau:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
Con trỏ thô (đôi khi được gọi là "con trỏ kế thừa" hoặc "Con trỏ C") cung cấp hành vi con trỏ 'xương trần' và là một nguồn phổ biến của lỗi và rò rỉ bộ nhớ. Con trỏ thô không cung cấp phương tiện để theo dõi quyền sở hữu tài nguyên và nhà phát triển phải gọi 'xóa' theo cách thủ công để đảm bảo chúng không tạo ra rò rỉ bộ nhớ. Điều này trở nên khó khăn nếu tài nguyên được chia sẻ vì có thể khó khăn để biết liệu có bất kỳ đối tượng nào vẫn đang trỏ đến tài nguyên hay không. Vì những lý do này, các con trỏ thô thường nên tránh và chỉ được sử dụng trong các phần quan trọng về hiệu năng của mã với phạm vi giới hạn.
Con trỏ duy nhất là một con trỏ thông minh cơ bản 'sở hữu' con trỏ thô bên dưới tài nguyên và chịu trách nhiệm gọi xóa và giải phóng bộ nhớ được phân bổ một khi đối tượng 'sở hữu' con trỏ duy nhất nằm ngoài phạm vi. Tên 'duy nhất' chỉ thực tế là chỉ một đối tượng có thể 'sở hữu' con trỏ duy nhất tại một thời điểm nhất định. Quyền sở hữu có thể được chuyển sang một đối tượng khác thông qua lệnh di chuyển, nhưng một con trỏ duy nhất không bao giờ có thể được sao chép hoặc chia sẻ. Vì những lý do này, các con trỏ duy nhất là một thay thế tốt cho các con trỏ thô trong trường hợp chỉ có một đối tượng cần con trỏ tại một thời điểm nhất định và điều này làm giảm bớt nhà phát triển khỏi nhu cầu giải phóng bộ nhớ ở cuối vòng đời của đối tượng sở hữu.
Con trỏ dùng chung là một loại con trỏ thông minh tương tự như con trỏ duy nhất, nhưng cho phép nhiều đối tượng có quyền sở hữu đối với con trỏ dùng chung. Giống như con trỏ duy nhất, các con trỏ được chia sẻ có trách nhiệm giải phóng bộ nhớ được phân bổ một khi tất cả các đối tượng được thực hiện trỏ đến tài nguyên. Nó thực hiện điều này với một kỹ thuật gọi là đếm tham chiếu. Mỗi khi một đối tượng mới có quyền sở hữu con trỏ dùng chung, số tham chiếu sẽ tăng lên một. Tương tự, khi một đối tượng đi ra khỏi phạm vi hoặc dừng trỏ đến tài nguyên, số tham chiếu bị giảm đi bởi một đối tượng. Khi số tham chiếu đạt đến 0, bộ nhớ được phân bổ sẽ được giải phóng. Vì những lý do này, con trỏ dùng chung là một loại con trỏ thông minh rất mạnh nên được sử dụng bất cứ lúc nào nhiều đối tượng cần trỏ đến cùng một tài nguyên.
Cuối cùng, con trỏ yếu là một loại con trỏ thông minh khác, thay vì chỉ trực tiếp đến một tài nguyên, chúng trỏ đến một con trỏ khác (yếu hoặc dùng chung). Con trỏ yếu không thể truy cập trực tiếp vào một đối tượng, nhưng chúng có thể cho biết liệu đối tượng đó có còn tồn tại hay đã hết hạn. Một con trỏ yếu có thể được chuyển đổi tạm thời thành một con trỏ dùng chung để truy cập vào đối tượng trỏ tới (miễn là nó vẫn tồn tại). Để minh họa, hãy xem xét ví dụ sau:
Trong ví dụ này, bạn có một con trỏ yếu cho Cuộc họp B. Bạn không phải là "chủ sở hữu" trong Cuộc họp B để nó có thể kết thúc mà không có bạn và bạn không biết liệu nó có kết thúc hay không trừ khi bạn kiểm tra. Nếu nó chưa kết thúc, bạn có thể tham gia và tham gia, nếu không, bạn không thể. Điều này khác với việc có một con trỏ dùng chung cho Cuộc họp B vì khi đó bạn sẽ là "chủ sở hữu" trong cả Cuộc họp A và Cuộc họp B (tham gia cả hai cùng một lúc).
Ví dụ minh họa cách một con trỏ yếu hoạt động và hữu ích khi một đối tượng cần phải là người quan sát bên ngoài , nhưng không muốn chịu trách nhiệm chia sẻ quyền sở hữu. Điều này đặc biệt hữu ích trong kịch bản mà hai đối tượng cần trỏ vào nhau (còn gọi là tham chiếu vòng tròn). Với các con trỏ được chia sẻ, không đối tượng nào có thể được giải phóng vì chúng vẫn được 'mạnh' chỉ vào đối tượng khác. Khi một trong các con trỏ là một con trỏ yếu, đối tượng giữ con trỏ yếu vẫn có thể truy cập vào đối tượng khác khi cần, miễn là nó vẫn tồn tại.
Ngoài các trường hợp sử dụng hợp lệ đã được đề cập khác std::weak_ptr
là một công cụ tuyệt vời trong môi trường đa luồng, bởi vì
std::shared_ptr
kết hợp với std::weak_ptr
an toàn chống lại con trỏ lơ lửng - ngược lại với std::unique_ptr
kết hợp với con trỏ thôstd::weak_ptr::lock()
là một hoạt động nguyên tử (xem thêm Về an toàn luồng của yếu_ptr )Xem xét một tác vụ để tải tất cả các hình ảnh của một thư mục (~ 10.000) đồng thời vào bộ nhớ (ví dụ như bộ đệm hình thu nhỏ). Rõ ràng cách tốt nhất để làm điều này là một luồng điều khiển, xử lý và quản lý hình ảnh, và nhiều luồng công nhân, tải hình ảnh. Bây giờ đây là một nhiệm vụ dễ dàng. Đây là một triển khai rất đơn giản ( join()
vv được bỏ qua, các luồng sẽ phải được xử lý khác nhau trong một triển khai thực sự, v.v.)
// a simplified class to hold the thumbnail and data
struct ImageData {
std::string path;
std::unique_ptr<YourFavoriteImageLibData> image;
};
// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
for( auto& imageData : imagesToLoad )
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas =
splitImageDatas( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
Nhưng nó trở nên phức tạp hơn nhiều, nếu bạn muốn làm gián đoạn việc tải hình ảnh, ví dụ vì người dùng đã chọn một thư mục khác. Hoặc thậm chí nếu bạn muốn phá hủy người quản lý.
Bạn cần giao tiếp luồng và phải dừng tất cả các luồng của trình tải, trước khi bạn có thể thay đổi m_imageDatas
trường của mình . Nếu không, các trình tải sẽ tiếp tục tải cho đến khi tất cả các hình ảnh được thực hiện - ngay cả khi chúng đã lỗi thời. Trong ví dụ đơn giản, điều đó sẽ không quá khó, nhưng trong một môi trường thực tế, mọi thứ có thể phức tạp hơn nhiều.
Các luồng có thể là một phần của nhóm luồng được sử dụng bởi nhiều người quản lý, trong đó một số bị dừng và một số thì không. Thông số đơn giản imagesToLoad
sẽ là một hàng đợi bị khóa, trong đó các trình quản lý đẩy các yêu cầu hình ảnh của họ từ các luồng điều khiển khác nhau với các độc giả xuất hiện các yêu cầu - theo thứ tự tùy ý - ở đầu kia. Và do đó, việc giao tiếp trở nên khó khăn, chậm chạp và dễ bị lỗi. Một cách rất thanh lịch để tránh mọi giao tiếp bổ sung trong những trường hợp như vậy là sử dụng std::shared_ptr
kết hợp với std::weak_ptr
.
// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
for( auto& imageDataWeak : imagesToLoad ) {
std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
if( !imageData )
continue;
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas =
splitImageDatasToWeak( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
Việc thực hiện này gần như dễ dàng như lần đầu tiên, không cần bất kỳ giao tiếp luồng bổ sung nào và có thể là một phần của nhóm luồng / hàng đợi trong một triển khai thực sự. Vì các hình ảnh hết hạn bị bỏ qua và các hình ảnh không hết hạn được xử lý, các luồng không bao giờ phải dừng lại trong quá trình hoạt động bình thường. Bạn luôn có thể thay đổi đường dẫn hoặc hủy các trình quản lý của mình một cách an toàn, vì trình đọc fn sẽ kiểm tra, nếu con trỏ sở hữu chưa hết hạn.
http://en.cppreference.com/w/cpp/memory/weak_ptr std :: yếu_ptr là một con trỏ thông minh chứa tham chiếu không sở hữu ("yếu") đến một đối tượng được quản lý bởi std :: shared_ptr. Nó phải được chuyển đổi thành std :: shared_ptr để truy cập đối tượng được tham chiếu.
std :: yếu_ptr mô hình quyền sở hữu tạm thời: khi một đối tượng chỉ cần được truy cập nếu nó tồn tại và nó có thể bị xóa bất cứ lúc nào bởi người khác, std :: yếu_ptr được sử dụng để theo dõi đối tượng và nó được chuyển đổi thành std: : shared_ptr để nhận quyền sở hữu tạm thời. Nếu std :: shared_ptr ban đầu bị hủy vào thời điểm này, thời gian tồn tại của đối tượng sẽ được kéo dài cho đến khi std :: shared_ptr tạm thời cũng bị phá hủy.
Ngoài ra, std :: yếu_ptr được sử dụng để phá vỡ các tham chiếu vòng tròn của std :: shared_ptr.
Có một nhược điểm của con trỏ dùng chung: shared_pulum không thể xử lý phụ thuộc chu kỳ cha-con. Có nghĩa là nếu lớp cha sử dụng đối tượng của lớp con bằng cách sử dụng một con trỏ dùng chung, trong cùng một tệp nếu lớp con sử dụng đối tượng của lớp cha. Con trỏ dùng chung sẽ không thể phá hủy tất cả các đối tượng, thậm chí con trỏ dùng chung hoàn toàn không gọi hàm hủy trong kịch bản phụ thuộc chu kỳ. về cơ bản con trỏ chia sẻ không hỗ trợ cơ chế đếm tham chiếu.
Hạn chế này chúng ta có thể khắc phục bằng cách sử dụng yếu.
weak_ptr
thỏa thuận với sự phụ thuộc vòng tròn mà không có sự thay đổi nào trong logic chương trình như là một sự thay thế thả vào cho shared_ptr
? :-)
Khi chúng ta không muốn sở hữu đối tượng:
Ví dụ:
class A
{
shared_ptr<int> sPtr1;
weak_ptr<int> wPtr1;
}
Trong lớp trên, wPtr1 không sở hữu tài nguyên được trỏ bởi wPtr1. Nếu tài nguyên bị xóa thì wPtr1 đã hết hạn.
Để tránh phụ thuộc vòng tròn:
shard_ptr<A> <----| shared_ptr<B> <------
^ | ^ |
| | | |
| | | |
| | | |
| | | |
class A | class B |
| | | |
| ------------ |
| |
-------------------------------------
Bây giờ nếu chúng ta tạo shared_ptr của lớp B và A, thì use_count của cả hai con trỏ là hai.
Khi shared_ptr đi ra ngoài phạm vi od, số lượng vẫn còn 1 và do đó đối tượng A và B không bị xóa.
class B;
class A
{
shared_ptr<B> sP1; // use weak_ptr instead to avoid CD
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void setShared(shared_ptr<B>& p)
{
sP1 = p;
}
};
class B
{
shared_ptr<A> sP1;
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void setShared(shared_ptr<A>& p)
{
sP1 = p;
}
};
int main()
{
shared_ptr<A> aPtr(new A);
shared_ptr<B> bPtr(new B);
aPtr->setShared(bPtr);
bPtr->setShared(aPtr);
return 0;
}
đầu ra:
A()
B()
Như chúng ta có thể thấy từ đầu ra rằng con trỏ A và B không bao giờ bị xóa và do đó bộ nhớ bị rò rỉ.
Để tránh vấn đề như vậy, chỉ cần sử dụng yếu_ptr trong lớp A thay vì shared_ptr sẽ có ý nghĩa hơn.
Tôi thấy std::weak_ptr<T>
như một tay cầm một std::shared_ptr<T>
: Nó cho phép tôi để có được std::shared_ptr<T>
nếu nó vẫn còn tồn tại, nhưng nó sẽ không kéo dài tuổi thọ của pin. Có một số tình huống khi quan điểm như vậy là hữu ích:
// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;
// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.
struct Widget {
std::weak_ptr< Texture > texture_handle;
void render() {
if (auto texture = texture_handle.get(); texture) {
// do stuff with texture. Warning: `texture`
// is now extending the lifetime because it
// is a std::shared_ptr< Texture >.
} else {
// gracefully degrade; there's no texture.
}
}
};
Một kịch bản quan trọng khác là phá vỡ các chu kỳ trong cấu trúc dữ liệu.
// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > next;
std::shared_ptr< Node > prev;
};
// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::shared_ptr< Node > next;
std::weak_ptr< Node > prev;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::weak_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
Herb Sutter có một bài nói chuyện tuyệt vời giải thích việc sử dụng tốt nhất các tính năng ngôn ngữ (trong trường hợp này là con trỏ thông minh) để đảm bảo Leak Freedom theo mặc định (có nghĩa là: mọi thứ nhấp vào vị trí bằng cách xây dựng; bạn khó có thể làm hỏng nó). Nó là phải xem.