Vấn đề Memory Leak và sử dụng smart pointer trong lập trình C++

From CodeForLife
Jump to: navigation, search

Vấn đề Memory Leak

Memory Leak (Rò rỉ bộ nhớ) là một kiểu Resource Leak xảy ra khi một ứng dụng quản lý việc cấp phát bộ nhớ không đúng, tức là một vùng nhớ không cần sử dụng nhưng lại không được giải phóng. Ứng dụng sẽ sử dụng nhiều bộ nhớ hơn, và vùng nhớ bị rò rỉ này sẽ không thể được sử dụng lại. Nếu hệ thống chạy lâu, và memory leak xảy ra ở code lặp thì một thời gian sẽ làm hết RAM của hệ thống và gây chết hệ điều hành.

Trong lập trình C/C++ thì vấn đề quản lý bộ nhớ tránh hiện tượng Memory Leak là bài toán lan giải của nhiều lập trình viên. Đặc biệt khi code có nhiều nhánh phức tạp, hay gọi các hàm có thể gây exception. Việc xử lý không cẩn thận dù chỉ 1 nhánh cũng sẽ gây ra hiện tượng Memory Leak. Đoạn mã sau là một ví dụ điển hinh về khả năng có thể bị Memory Leak:

void memory_leak() 
{
	ClassA * ptr = new ClassA;
	...
	delete ptr;
}

Trong đoạn code trên, phần xử lý giữa đoạn cấp phát và giải phóng, có thể xảy ra các exception, khi đó lệnh delete ptr sẽ không được gọi và gây ra hiện tượng Resource Leak. Thông thường, trong trường hợp này, ta phải bắt tất cả các ngoại lệ có thể xảy ra như sau:

void memory_leak() 
{
	ClassA * ptr = new ClassA;
	try {
	     ...
	}
	catch(...) {
		delete ptr;
		throw;
	}
	delete ptr;
}

Ngoài ra, nhưng hàm có nhiều điểm thoát (Exit function) cũng rất dễ gây hiện tượng Resource Leak khi không đảm bảo giải phóng ở tất cả các case khi kết thúc hàm.

Vì vậy, Smart Pointer ra đời với mục đích tránh hiện tượng Memory Leak và giúp lập trình viên quản lý con trỏ dễ dàng hơn. Smart Pointer là một đối tượng hoạt động giống như con trỏ, nhưng lại tránh được vấn đề cố hữu của con trỏ. Smart Pointer đơn giản nó chứa một con trỏ gốc như là một thành viên của dữ liệu, đồng thời cung cấp một tập các toán tử giúp nó hoạt động giống một con trỏ nhất.

Sử dụng auto_ptr trong thư viện chuản C++

auto_ptrtemplate class được cung cấp bởi thư viện chuẩn C++ std (Standard Template Library), trong tệp tiêu đề <memory>. Đối tượng auto_ptr nắm giữ đối tượng được tham chiếu tới, vì thế đối tượng tham chiếu tới sẽ tự động được hủy khi auto_ptr bị hủy. Với ví dụ ở trên, bạn có thể viết lại sử dụng auto_ptr như dưới, khi đó bạn không cần lệnh deletecũng như không cần sử dụng try ... catchnữa:

#include <memory>
void memory_leak() 
{
	std::auto_ptr<ClassA> ptr(new ClassA);
	...
}

Như trên, ptr là biến local, biến này tự động hủy khi ra khỏi đối tượng, khi ptr bị hủy, hàm hủy của nó sẽ được gọi và hàm này sẽ tự động hủy đối tượng nó tham chiếu đến, chính là con trỏ trỏ tới lớp ClassA.

Một auto_ptr có giao diện như một con trỏ thông thường, toán tử * để tham chiếu tới đối tượng và toán tử -> để truy cập đến một thành viên của đối tượng. Nhưng các toán tử thao tác số học như ++, --, ... không được định nghĩa.

Một vấn đề chú ý khi sử dụng là auto_ptr không cho phép chúng ta khởi tạo đối tượng bằng sử dụng toán tử gán:

std::auto_ptr<ClassA> ptr1(new ClassA);	        // RIGHT
std::auto_ptr<ClassA> ptr1 = new ClassA;	    // WRONG

Một auto_ptr tại một thời điểm chỉ có thể sở hữu một con trỏ đối tượng, Chính vì vậy đối tượng không thể là mảng. Và bằng cách sử dụng toán tử gán (assignment operator) hoặc hàm khởi tạo copy (copy constructor), một auto_ptr có thểchuyển quyền sở hữu việc quản lý vùng nhớ động cho đối tượng, khi đó đối tượng auto_ptr cuối cùng sẽ thực hiện giải phóng vùng nhớ động mà mình đang quản lý này.

Mặc dù auto_ptr giúp quản lý hiệu quả vùng nhớ được cấp phát động, nhưng nó có một số hạn chế:

  • Một auto_ptr không thể trỏ tới một mảng. Khi xóa một con trỏ mảng chúng ta phải sử dụng delete[] để đảm bảo hàm hủy được gọi ở tất cả các đối tượng trong mảng, nhưng auto_ptr lại sử dụng delete.
  • auto_ptr không thể sử dụng với các phần tử trong một một STL Container. Khi một auto_ptr được sao chép, quyền quản lý được chuyển giao cho đối tượng auto_ptr mới, và đối con trỏ mà đối tượng cũ quản lý được thiết lập tới NULL. Hay nói cách khác, auto_ptr không làm việc trong các STL container vì các container hay các thuật toán cài đặt được lưu trư trên các phần tử này, vì sao chép không đảm bảo tạo ra bản sao hợp lệ.

Trong C++11, tích hợp hầu hết các thành phần trong gói TR1 (C++ Technical Report 1), mở rộng thêm smart pointer hữu ích khác, đó là:: unique_ptrshared_ptrweak_ptr. Trong C++11, std:auto_ptr bị hạn chế sử dụng và bị loại bỏ hẳn trong C++17.

Sử dụng unique_ptr trong C++11

auto_ptr không sử dụng trong C++11, vì thế unique_ptr ra đời với mục đích thay thế cho auto_ptr. unique_ptr không cho phép chuyển giao ngầm con trỏ mà nó quản lý thông qua phép gán như với auto_ptr, mà phải rõ ràng thông qua hàm std::move.

Với auto_ptr bạn thực hiện như sau:

std::auto_ptr<int> p(new int);
std::auto_ptr<int> p2 = p; 

Còn với unique_ptr, bạn phải thực hiện như sau:

std::unique_ptr<int> p(new int);
std::unique_ptr<int> p2 = std::move(p);

Sử dụng shared_ptr trong C++11

Như ở trên đã nói, thông qua phép gán hoặc hàm khởi tạo sao chép, auto_ptr sẽ chuyển giao quyền quản lý con trỏ sang auto_ptr khác. Bạn có thể kiểm tra qua ví dụ dưới đây:

#include <iostream>
#include <memory>

using namespace std;
class A{};

int main()
{
	auto_ptr<A> pA(new A);

	cout << pA.get() << endl;

	auto_ptr<A> pB(pA);

	cout << pA.get() << endl;
	cout << pB.get() << endl;

	return 0;
}

Kết quả chạy là:

0x1fe2c20
0
0x1fe2c20

Như vây, không thể có nhiều auto_ptr cùng quản lý cùng một con trỏ, đây là một cách quản lý tài nguyên chưa tối ưu. Để thay thế cho auto_ptr, chúng ta sử dụng shared_ptr, đây là một con trỏ thông minh có bộ đếm các tham chiếu, nó sẽ theo dõi xem có bao nhiêu shared_ptr cùng trỏ tới một tài nguyên cụ thể và tự động giải phóng tài nguyên này khi không còn shared_ptr nào tham chiếu tới nó.

Bằng việc thay thế auto_ptr bằng shared_ptr với cùng đoạn code trên:

#include <iostream>
#include <memory>

using namespace std;

class A {
};

int main() {
    shared_ptr<A> pA(new A);

    cout << pA.get() << endl;

    shared_ptr<A> pB(pA);

    cout << pA.get() << endl;
    cout << pB.get() << endl;

    return 0;
}

Khi chạy bạn sẽ thấy output như sau:

0x1248c20
0x1248c20
0x1248c20

shared_ptr tự động xác định được tài nguyên không được tham chiếu và gọi toán tử delete để xóa. Ngoài ra shared_ptr còn cho phép định nghĩa hàm xóa đối tượng, việc này giúp shared_ptr có thể quản lý các đối tượng phức tạp khác như mảng,... Bạn xem ví dụ dưới:

#include <iostream>
#include <memory>

using namespace std;

class A {
public:
    A() {
        cout << "Constructor of A" << endl;
    }
    
    virtual ~A() {
        cout << "Destructor of A" << endl;
    }
};

void delete_arr_a(A* p) {
    if (p!=NULL) {
        delete [] p;
    }
}

int main() {
    shared_ptr<A> pA(new A[10], delete_arr_a);
    cout << "Array data: " << pA.get() << endl;

    return 0;
}

Khi chạy bạn sẽ thấy output như sau:

Constructor of A
Constructor of A
Constructor of A
Constructor of A
Constructor of A
Constructor of A
Constructor of A
Constructor of A
Constructor of A
Constructor of A
Array data: 0x1ed7c28
Destructor of A
Destructor of A
Destructor of A
Destructor of A
Destructor of A
Destructor of A
Destructor of A
Destructor of A
Destructor of A
Destructor of A

Tương tự trên, để tổng quát, bạn có thể dễ dàng tạo lớp template shared_array_ptr để áp dụng chung cho kiểu mảng:

#include <iostream>
#include <memory>

using namespace std;

template<typename _Tp>
class shared_array_ptr : public shared_ptr<_Tp> {
private:
    static void delete_array_ptr(_Tp* p) {
        cout << "Delete array data: " << p << endl;
        if (p!=NULL) {
            delete [] p;
        }
    }

public:
    explicit shared_array_ptr(_Tp* __p)
    : shared_ptr<_Tp>(__p, delete_array_ptr) { }
};

class A {
public:
    A() {
        cout << "Constructor of A" << endl;
    }
    
    virtual ~A() {
        cout << "Destructor of A" << endl;
    }
};

int main() {
    shared_array_ptr<A> pA(new A[10]);
    cout << "Array data: " << pA.get() << endl;

    return 0;
}

Sử dụng weak_ptr trong C++11

weak_ptr là con trỏ không sở hữu cũng không hủy đối tượng tham chiếu tới. Một weak_ptr trỏ tới tài nguyên được quản lý bởi shared_ptr mà không có bất kỳ trách nhiệm nào với nó. Số tham chiếu cho shared_ptr không tăng khi weak_ptr tham chiếu đến nó, điều này có nghĩa tài nguyên được quản lý bởi shared_ptr có thể được giải phóng trong khi vẫn có weak_ptr trỏ tới nó. Khi shared_ptr cuối cùng bị hủy, tài nguyên được giải phóng, và các weak_ptr còn lại sẽ được thiết lập tới NULL.

Một weak_ptr không thể truy cập trực tiếp tới tài nguyên mà nó trỏ tới, chúng ta phải tạo một shared_ptr từ weak_ptr để truy cập tới tài nguyên, có hai cách thực hiện việc này:

  • C1: Bạn truyền đối tượng weak_ptr là tham số trong hàm khởi tạo của shared_ptr. Việc này sẽ tạo ra một shared_ptr trở tới tài nguyên mà weak_ptr đang trỏ tới, đồng thời biến đếm tham chiếu tăng lên. Khi tài nguyên đã bị giải phóng, hàm khởi tạo của shared_ptr sẽ bắn ra ngoại lệ std::bad_weak_ptr.
  • C2: Bạn gọi hàm thành viên lock của weak_ptr, nó trả về shared_ptr trở tới tài nguyên của weak_ptr. Nếu weak_ptr trở tới một tài nguyên đã được giải phóng, hàm lock sẽ trả về một shared_ptr rỗng.

weak_ptr nên được sử dụng trong các tình huống chúng ta cần giám sát tài nguyên nhưng không muốn quản lý nó. Một trường hợp sử dụng nữa là để tránh memory leak gây ra bởi tham chiếu vòng.

Ví dụ sau ta sử dụng weak_ptr trong dữ liệu tham chiếu vòng, một tình huống mà hai đối tượng nội bộ tham chiếu nhau. Trong ví dụ này, chúng ta định nghĩa hai lớp Singer Song. Mỗi lớp có một con trỏ trỏ tới thể hiện của lớp khác. Điều này sẽ tạo ra một tham chiếu vòng giữa hai lớp. Vì thế chúng ta chú ý sử dụng weak_ptr và shared_ptr để nắm giữ các tham chiếu chéo tới mỗi lớp.

Lớp Singer Song định nghĩa hàm hủy hiển thị thông báo thể hiện mỗi lớp đã được hủy. Mỗi lớp cũng định nghĩa các hàm thành viên để in tiêu đề bài hát và tên ca sỹ. Vì chúng ta không thể truy cập trực tiếp tới weak_ptr, chúng ta tạo ra shared_ptr thông qua hàm lock của weak_ptr. Nếu tài nguyên tham chiếu bới weak_ptr không tồn tại shared_ptr sẽ trỏ tới NULL và điều kiện không đúng. Ngược lại, một shared_ptr chứa con trỏ hợp lệ sẽ được tạo, và chúng ta có thể truy cập được tài nguyên. Nếu điều kiện đúng (tức là cả songPtrsingerPtr khác NULL), chúng ta in số tham chiếu để thể hiện rằng 01 shared_ptr được tạo, sau đó chúng ta in tiêu đề bài hát và tên ca sỹ. shared_ptr sẽ bị hủy khi kết thúc hàm và số tham chiếu sẽ giảm đi 01.

Singer.h:

#ifndef SINGER_H
#define SINGER_H

#include <iostream>
#include <memory>

using namespace std;

class Song;

class Singer {
public:
    Singer(const string &singerName);
    ~Singer();
    void printSongTitle();

    string name;
    weak_ptr<Song> weakSongPtr;
    shared_ptr<Song> sharedSongPtr;
};
#endif /* SINGER_H */

Song.h:

#ifndef SONG_H
#define SONG_H

#include <iostream>
#include <memory>

using namespace std;

class Singer; // forward declaration

class Song {
public:
    Song(const string &songTitle);
    ~Song();
    void printSingerName();
    
    string title;
    weak_ptr<Singer> weakSingerPtr;
    shared_ptr<Singer> sharedSingerPtr;
};

#endif /* SONG_H */

Singer.cpp:

#include "Singer.h"
#include "Song.h"

Singer::Singer(const string &singerName) : name(singerName) {
    cout << "Singer constructor: " << name << endl;
}

Singer::~Singer() {
    cout << "Singer destructor: " << name << endl;
}

void Singer::printSongTitle() {
    if (shared_ptr<Song> songPtr = weakSongPtr.lock()) {
        // if weaksongPtr.lock() returns a non-empty shared_ptr
        cout << "Reference count for song " << songPtr->title
                << " is " << songPtr.use_count() << "." << endl;
        cout << "Singer " << name << " wrote the song " << songPtr->title << "\n\n";
    } else {
        // weaksongPtr points to NULL
        cout << "This Singer has no song." << endl;
    }
}

Song.cpp:

#include "Song.h"
#include "Singer.h"

Song::Song(const string &songTitle) : title(songTitle) {
    cout << "Song constructor: " << title << endl;
}

Song::~Song() {
    cout << "Song destructor: " << title << endl;
}

void Song::printSingerName() {
    // if weakSingerPtr.lock() returns a non-empty shared_ptr
    if (shared_ptr<Singer> singerPtr = weakSingerPtr.lock()) {
        // show the reference count increase and print the Singer's name
        cout << "Reference count for Singer " << singerPtr->name
                << " is " << singerPtr.use_count() << "." << endl;
        cout << "The Song " << title << " was written by "
                << singerPtr->name << "\n" << endl;
    }
    else {
        // weakSingerPtr points to NULL
        cout << "This Song has no Singer." << endl;
    }
}

main.cpp:

#include "Singer.h"
#include "Song.h"

int main() {
    cout << "Creating a Song and an Singer ..." << endl;
    shared_ptr<Song> songPtr(new Song("The Boys"));
    shared_ptr<Singer> singerPtr(new Singer("Girls Generation"));

    cout << "\nReferencing the Song and Singer to each other..." << endl;
    songPtr->weakSingerPtr = singerPtr;
    singerPtr->weakSongPtr = songPtr;

    cout << "\nSetting the shared_ptr data members to create the memory leak..." << endl;

    songPtr->sharedSingerPtr = singerPtr;
    singerPtr->sharedSongPtr = songPtr;

    cout << "Reference count for SongPtr and SingerPtr should be one, but ... " << endl;
    cout << "Reference count for Song " << songPtr->title << " is "
            << songPtr.use_count() << endl;
    cout << "Reference count for Singer " << singerPtr->name << " is "
            << singerPtr.use_count() << "\n" << endl;

    cout << "\nAccess the Singer's name and the Song's title through "
            << "weak_ptrs." << endl;
    songPtr->printSingerName();
    singerPtr->printSongTitle();

    cout << "Reference count for each shared_ptr shoulb be back to one:" << endl;
    cout << "Reference count for Song " << songPtr->title << " is "
            << songPtr.use_count() << endl;
    cout << "Reference count for Singer " << singerPtr->name << " is "
            << singerPtr.use_count() << "\n" << endl;

    // the shared_ptrs go out of scope, the Song and Singer are destroyed
    cout << "The shared_ptrs are going out of scope." << endl;

    return 0;
}

Trong hàm main() chúng ta thấy có memory leak gây ra bởi tham chiếu vòng giữa lớp Singer và Song. Hai dong sau:

shared_ptr<Song> songPtr(new Song("The Boys"));
shared_ptr<Singer> singerPtr(new Singer("Girls Generation"));

tạo ra shared_ptr trỏ tới thể hiện mỗi lớp. Tiếp đó biến thành viên weak_ptr được thiết lập:

songPtr->weakSingerPtr = singerPtr;
singerPtr->weakSongPtr = songPtr;

Sau đó biến thành viên shared_ptr của mỗi lớp cũng được thiết lập:

songPtr->sharedSingerPtr = singerPtr;
singerPtr->sharedSongPtr = songPtr;

Chính hai dòng tạo ra tham chiếu vòng sử dung shared_ptr, và gây ra memory_leak. Tại sao vậy? Khi hàm main kết thúc, thì hai biến shared_ptr trong hàm main sẽ bị hủy, nhưng vẫn còn biến thành viên shared_ptr của hai lớp đang trỏ đến lớp còn lại, vì thế đối tượng không bị xóa. Muốn đối tượng bị xóa thì thành viên shared_ptr phải bị hủy, nhưng thành viên shared_ptr này chỉ bị hủy khi đối tượng bị xóa, do đó đối tượng không thể bị xóa. Khi bạn chạy code trên, output như dưới, bạn sẽ thấy hàm hủy của hai lớp Singer Song không được gọi:

Creating a Song and an Singer ...
Song constructor: The Boys
Singer constructor: Girls Generation

Referencing the Song and Singer to each other...

Setting the shared_ptr data members to create the memory leak...
Reference count for SongPtr and SingerPtr should be one, but ... 
Reference count for Song The Boys is 2
Reference count for Singer Girls Generation is 2


Access the Singer's name and the Song's title through weak_ptrs.
Reference count for Singer Girls Generation is 3.
The Song The Boys was written by Girls Generation

Reference count for song The Boys is 3.
Singer Girls Generation wrote the song The Boys

Reference count for each shared_ptr shoulb be back to one:
Reference count for Song The Boys is 2
Reference count for Singer Girls Generation is 2

The shared_ptrs are going out of scope.

Vì thế khi thực hiện tham chiếu vòng người ta không sử dụng shared_ptr mà sử dụng weak_ptr. Trong đoạn code trên bạn bỏ hai biến thành viên shared_ptr của lớp Song và Singer, đồng thời trong hàm main() bạn comment hai dong sau:

//    songPtr->sharedSingerPtr = singerPtr;
//    singerPtr->sharedSongPtr = songPtr;

Bạn sẽ thấy không còn bị memory leak nữa, khi chạy lại hàm hủy của hai lớp Song Singer được gọi. Output khi chạy như sau:

Creating a Song and an Singer ...
Song constructor: The Boys
Singer constructor: Girls Generation

Referencing the Song and Singer to each other...

Setting the shared_ptr data members to create the memory leak...
Reference count for SongPtr and SingerPtr should be one, but ... 
Reference count for Song The Boys is 1
Reference count for Singer Girls Generation is 1


Access the Singer's name and the Song's title through weak_ptrs.
Reference count for Singer Girls Generation is 2.
The Song The Boys was written by Girls Generation

Reference count for song The Boys is 2.
Singer Girls Generation wrote the song The Boys

Reference count for each shared_ptr shoulb be back to one:
Reference count for Song The Boys is 1
Reference count for Singer Girls Generation is 1

The shared_ptrs are going out of scope.
Singer destructor: Girls Generation
Song destructor: The Boys

Tạo shared_ptr bằng hàm std::make_shared và std::allocate_shared

Ngoài cách tạo shared_ptr thông qua hàm khởi tạo, trong C++11 cung cấp hai hàm std::make_sharedstd::allocate_shared để tạo các shared_ptr. Hai hàm này đề tạo shared_ptr nhưng hàm make_shared sử dụng new để tạo đối tượng, còn hàm allocate_shared dùng alloc để tạo đối tượng.

Ví dụ sử dụng make_shared:

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> foo = std::make_shared<int> (10);
    // same as:
    std::shared_ptr<int> foo2(new int(10));

    auto bar = std::make_shared<int> (20);

    auto baz = std::make_shared<std::pair<int, int>> (30, 40);

    std::cout << "*foo: " << *foo << '\n';
    std::cout << "*bar: " << *bar << '\n';
    std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';

    return 0;
}

Ví dụ sử dụng allocate_shared:

#include <iostream>
#include <memory>

int main() {
    std::allocator<int> alloc; // the default allocator for int
    std::default_delete<int> del; // the default deleter for int

    std::shared_ptr<int> foo = std::allocate_shared<int> (alloc, 10);

    auto bar = std::allocate_shared<int> (alloc, 20);

    auto baz = std::allocate_shared<std::pair<int, int>> (alloc, 30, 40);

    std::cout << "*foo: " << *foo << '\n';
    std::cout << "*bar: " << *bar << '\n';
    std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';

    return 0;
}

Việc sử dụng hai hàm make_sharedallocate_shared sẽ an toàn hơn khi có exception xảy ra. Ví dụ đoạn code sau:

void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }

F(std::shared_ptr<Lhs>(new Lhs("foo")), std::shared_ptr<Rhs>(new Rhs("bar")));

C++ cho phép thực hiện các biểu thức con một cách tùy ý, do đó thứ tự có thể xẩy ra là:

1. new Lhs("foo"))
2. new Rhs("bar"))
3. std::shared_ptr<Lhs>
4. std::shared_ptr<Rhs>

Trong trường hợp này nếu có exception (out of memory) ở bước 2 thì sẽ gây hiện tượng memory leak. Bạn có thể fix bằng cách tách thành các lệnh riêng biệt như sau:

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

Hoặc bạn có thể fix bằng cách sử dụng make_shared:

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

Tôi đã thử viết đoạn code đánh giá hiệu năng khi tạo shared_ptr trực tiếp và khi tạo bằng make_shared thì thấy rằng sử dụng trực tiếp nhanh hơn nhiều so với sử dụng make_shared. Tôi thử chạy đoạn code sau:

#include <iostream>
#include <memory>
#include <time.h>

const int MAX_TEST = 30000000;

void test_make_shared() {
    clock_t begin = clock();
    for (int k = 0; k < MAX_TEST; ++k) {
        std::shared_ptr<int> foo = std::make_shared<int> (k);
    }
    clock_t end = clock();
    double time = (double)(end - begin) / CLOCKS_PER_SEC;
    std::cout << "Time for using make_shared: " << time << std::endl;
}

void test_normal_shared() {
    clock_t begin = clock();
    for (int k = 0; k < MAX_TEST; ++k) {
        std::shared_ptr<int> foo = std::shared_ptr<int>(new int(k));
    }
    clock_t end = clock();
    double time = (double)(end - begin) / CLOCKS_PER_SEC;
    std::cout << "Time for using construtor of shared_ptr: " << time << std::endl;
}

int main() {
    test_normal_shared();
    test_make_shared();

    return 0;
}

Kết quả chạy như sau:

Time for using construtor of shared_ptr: 4.22657
Time for using make_shared: 11.1286

 

Tham khảo: