Example Code: Ownership and Smart Pointers#

Manipulate data using a raw pointer (01_raw_pointer.cpp).#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <iomanip>
#include <cstdlib>

struct PlainData
{
    int buffer[1024*8];
}; /* end struct PlainData */

std::ostream & put_ptr(std::ostream & out, void * ptr)
{
    out << std::internal << std::setw(18) << std::setfill('0') << ptr;
    return out;
}

int main(int, char **)
{
    std::cout << "PlainData pointer initialized : ";
    // It is a good practice to initialize a raw pointer to nullptr.
    PlainData * ptr = nullptr;
    // Although nullptr will be integer 0, do not use the integer literal 0 or
    // the infamous macro NULL to represent nullity.
    put_ptr(std::cout, ptr) << std::endl;

    // The reason to not use 0 or NULL for the null pointer: they are not even
    // of a pointer type!
    static_assert(!std::is_pointer<decltype(0)>::value, "error");
    static_assert(!std::is_pointer<decltype(NULL)>::value, "error");
    // 0 is int
    static_assert(std::is_same<decltype(0), int>::value, "error");
    // int cannot be converted to a pointer.
    static_assert(!std::is_convertible<decltype(0), void *>::value, "error");
    // NULL is long
    static_assert(std::is_same<decltype(NULL), long>::value, "error");
    // long cannot be converted to a pointer, either.
    static_assert(!std::is_convertible<decltype(NULL), void *>::value, "error");

    // Although nullptr is of type std::nullptr_t, not exactly a pointer ...
    static_assert(std::is_same<decltype(nullptr), std::nullptr_t>::value, "error");
    static_assert(!std::is_pointer<decltype(nullptr)>::value, "error");
    // It can be converted to a pointer.
    static_assert(std::is_convertible<decltype(nullptr), void *>::value, "error");
    static_assert(std::is_convertible<decltype(nullptr), PlainData *>::value, "error");

    // Allocate memory for PlainData and get the returned pointer.
    std::cout << "PlainData pointer after malloc: ";
    ptr = static_cast<PlainData *>(malloc(sizeof(PlainData)));
    put_ptr(std::cout, ptr) << std::endl;

    // After free the memory, the pointer auto variable is not changed.
    std::cout << "PlainData pointer after free  : ";
    free(ptr);
    put_ptr(std::cout, ptr) << std::endl;

    // Use new to allocate for and construct PlainData and get the returned
    // pointer.
    std::cout << "PlainData pointer after new   : ";
    ptr = new PlainData();
    put_ptr(std::cout, ptr) << std::endl;

    // After delete, the pointer auto variable is not changed, either.
    std::cout << "PlainData pointer after delete: ";
    delete ptr;
    put_ptr(std::cout, ptr) << std::endl;

    return 0;
}
$ g++ 01_raw_pointer.cpp -o 01_raw_pointer -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror
Manipulate data using a reference (02_reference.cpp).#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <iomanip>
#include <cstdlib>

struct PlainData
{
    int buffer[1024*8];
}; /* end struct PlainData */

std::ostream & put_ptr(std::ostream & out, void * ptr)
{
    out << std::internal << std::setw(18) << std::setfill('0') << ptr;
    return out;
}

// The factory function for PlainData.
PlainData * make_data()
{
    PlainData * ptr = new PlainData();
    // (... work to be done before returning.)
    return ptr;
}

void manipulate_with_reference(PlainData & data)
{
    std::cout << "Manipulate with reference      : ";
    put_ptr(std::cout, &data) << std::endl;

    for (size_t it=0; it < 1024*8; ++it)
    {
        data.buffer[it] = it;
    }
    // (... more meaningful work before returning.)

    // We cannot delete an object passed in with a reference.
}

int main(int, char **)
{
    PlainData * ptr = nullptr;

    // Obtain the pointer to the object ('resource').
    ptr = make_data();
    std::cout << "PlainData pointer after factory: ";
    put_ptr(std::cout, ptr) << std::endl;

    manipulate_with_reference(*ptr);

    // A good habit when using raw pointer: destruct the object in the scope
    // that we obtain the pointer.  In this way, we don't forget to delete it
    // and avoid potential resource leak.
    delete ptr;
    std::cout << "PlainData pointer after delete : ";
    put_ptr(std::cout, ptr) << std::endl;

    return 0;
}
$ g++ 02_reference.cpp -o 02_reference -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror
The concept of ownership (03_ownership.cpp).#
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
#include <iostream>
#include <cstdlib>
#include <algorithm>

class Data
{

public:

    constexpr const static size_t NELEM = 1024*8;

    using iterator = int *;
    using const_iterator = const int *;

    Data()
    {
        std::fill(begin(), end(), 0);
        std::cout << "Data @" << this << " is constructed" << std::endl;
    }

    ~Data()
    {
        std::cout << "Data @" << this << " is destructed" << std::endl;
    }

    const_iterator cbegin() const { return m_buffer; }
    const_iterator cend() const { return m_buffer+NELEM; }
    iterator begin() { return m_buffer; }
    iterator end() { return m_buffer+NELEM; }

    size_t size() const { return NELEM; }
    int   operator[](size_t it) const { return m_buffer[it]; }
    int & operator[](size_t it)       { return m_buffer[it]; }

    bool is_manipulated() const
    {
        for (size_t it=0; it < size(); ++it)
        {
            const int v = it;
            if ((*this)[it] != v) { return false; }
        }
        return true;
    }

private:

    // A lot of data that we don't want to reconstruct.
    int m_buffer[NELEM];

}; /* end class Data */

void manipulate_with_reference(Data & data)
{
    std::cout << "Manipulate with reference: " << &data << std::endl;

    for (size_t it=0; it < data.size(); ++it)
    {
        data[it] = it;
    }
    // In a real consumer function we will do much more meaningful operations.

    // However, we cannot destruct an object passed in with a reference.
}

Data * worker1()
{
    // Create a new Data object.
    Data * data = new Data();

    // Manipulate the Data object.
    manipulate_with_reference(*data);

    return data;
}

/*
 * Code in this function is intentionally made to be lack of discipline to
 * demonstrate how ownership is messed up.
 */
void worker2(Data * data)
{
    // The prerequisite for the caller to write correct code is to read the
    // code and understand when the object is alive.
    if (data->is_manipulated())
    {
        delete data;
    }
    else
    {
        manipulate_with_reference(*data);
    }
}

int main(int, char **)
{
    Data * data = worker1();
    std::cout << "Data pointer after worker 1: " << data << std::endl;
    worker2(data);
    std::cout << "Data pointer after worker 2: " << data << std::endl;

    // You have to read the code of worker2 to know that data could be
    // destructed.  In addition, the Data class doesn't provide a
    // programmatical way to detect whether or not the object is alive.  The
    // design of Data, worker1, and worker2 makes it impossible to write
    // memory-safe code.
#ifdef CRASHME // The fenced code causes double free.
    delete data;
    std::cout << "Data pointer after delete: " << data << std::endl;
#endif
}
$ g++ 03_ownership.cpp -o 03_ownership -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror
Build 03_ownership.cpp but turn on the crashing macro.#
$ g++ 03_ownership.cpp -o 03_ownership -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror -DCRASHME
Use unique pointer (04_unique.cpp).#
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <memory>

class Data
{

public:

    constexpr const static size_t NELEM = 1024*8;

    using iterator = int *;
    using const_iterator = const int *;

    Data()
    {
        std::fill(begin(), end(), 0);
        std::cout << "Data @" << this << " is constructed" << std::endl;
    }

    ~Data()
    {
        std::cout << "Data @" << this << " is destructed" << std::endl;
    }

    const_iterator cbegin() const { return m_buffer; }
    const_iterator cend() const { return m_buffer+NELEM; }
    iterator begin() { return m_buffer; }
    iterator end() { return m_buffer+NELEM; }

    size_t size() const { return NELEM; }
    int   operator[](size_t it) const { return m_buffer[it]; }
    int & operator[](size_t it)       { return m_buffer[it]; }

    bool is_manipulated() const
    {
        for (size_t it=0; it < size(); ++it)
        {
            const int v = it;
            if ((*this)[it] != v) { return false; }
        }
        return true;
    }

private:

    // A lot of data that we don't want to reconstruct.
    int m_buffer[NELEM];

}; /* end class Data */

void manipulate_with_reference(Data & data)
{
    std::cout << "Manipulate with reference: " << &data << std::endl;

    for (size_t it=0; it < data.size(); ++it)
    {
        data[it] = it;
    }
    // In a real consumer function we will do much more meaningful operations.

    // However, we cannot destruct an object passed in with a reference.
}

static_assert
(
    sizeof(Data *) == sizeof(std::unique_ptr<Data>)
  , "unique_ptr should take only a word"
);

std::unique_ptr<Data> worker1()
{
    // Create a new Data object.
    std::unique_ptr<Data> data = std::make_unique<Data>();

    // Manipulate the Data object.
    manipulate_with_reference(*data);

    return data;
}

void worker2(std::unique_ptr<Data> data)
{
    if (data->is_manipulated())
    {
        data.reset();
    }
    else
    {
        manipulate_with_reference(*data);
    }
}

int main(int, char **)
{
    std::unique_ptr<Data> data = worker1();
    std::cout << "Data pointer after worker 1: " << data.get() << std::endl;

#ifdef COPYNOWORK
    worker2(data);
#else
    worker2(std::move(data));
#endif
    std::cout << "Data pointer after worker 2: " << data.get() << std::endl;

    data.reset();
    std::cout << "Data pointer after delete: " << data.get() << std::endl;
}
$ g++ 04_unique.cpp -o 04_unique -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror
Build 04_unique.cpp but turn on the crashing macro.#
$ g++ 04_unique.cpp -o 04_unique -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror -DCOPYNOWORK
Use shared pointer (05_shared.cpp).#
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <memory>

class Data
{

public:

    constexpr const static size_t NELEM = 1024*8;

    using iterator = int *;
    using const_iterator = const int *;

    Data()
    {
        std::fill(begin(), end(), 0);
        std::cout << "Data @" << this << " is constructed" << std::endl;
    }

    ~Data()
    {
        std::cout << "Data @" << this << " is destructed" << std::endl;
    }

    const_iterator cbegin() const { return m_buffer; }
    const_iterator cend() const { return m_buffer+NELEM; }
    iterator begin() { return m_buffer; }
    iterator end() { return m_buffer+NELEM; }

    size_t size() const { return NELEM; }
    int   operator[](size_t it) const { return m_buffer[it]; }
    int & operator[](size_t it)       { return m_buffer[it]; }

    bool is_manipulated() const
    {
        for (size_t it=0; it < size(); ++it)
        {
            const int v = it;
            if ((*this)[it] != v) { return false; }
        }
        return true;
    }

private:

    // A lot of data that we don't want to reconstruct.
    int m_buffer[NELEM];

}; /* end class Data */

void manipulate_with_reference(Data & data)
{
    std::cout << "Manipulate with reference: " << &data << std::endl;

    for (size_t it=0; it < data.size(); ++it)
    {
        data[it] = it;
    }
    // In a real consumer function we will do much more meaningful operations.

    // However, we cannot destruct an object passed in with a reference.
}

static_assert
(
    sizeof(Data *) < sizeof(std::shared_ptr<Data>)
  , "shared_ptr uses more than a word"
);

std::shared_ptr<Data> worker1()
{
    // Create a new Data object.
    std::shared_ptr<Data> data = std::make_shared<Data>();

    std::cout << "worker 1 data.use_count(): " << data.use_count() << std::endl;

    // Manipulate the Data object.
    manipulate_with_reference(*data);

    return data;
}

void worker2(std::shared_ptr<Data> data)
{
    std::cout << "worker 2 data.use_count(): " << data.use_count() << std::endl;

    if (data->is_manipulated())
    {
        data.reset();
    }
    else
    {
        manipulate_with_reference(*data);
    }
}

int main(int, char **)
{
    std::shared_ptr<Data> data = worker1();
    std::cout << "Data pointer after worker 1: " << data.get() << std::endl;

    worker2(data);
    std::cout << "Data pointer after worker 2: " << data.get() << std::endl;

    std::cout << "main data.use_count(): " << data.use_count() << std::endl;
    data.reset();
    std::cout << "Data pointer after reset from outside: " << data.get() << std::endl;
    std::cout << "main data.use_count(): " << data.use_count() << std::endl;
}
$ g++ 05_shared.cpp -o 05_shared -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror
Fully manage data object in shared pointer (01_fully.cpp).#
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <memory>

class Data
{

public:

    constexpr const static size_t NELEM = 1024*8;

    using iterator = int *;
    using const_iterator = const int *;

private:

    class ctor_passkey {};

public:

    Data() = delete;

    // TODO: Copyability and moveability should be considered, but we leave
    // them for now.

    static std::shared_ptr<Data> make()
    {
        std::shared_ptr<Data> ret = std::make_shared<Data>(ctor_passkey());
        return ret;
    }

    Data(ctor_passkey const &)
    {
        std::fill(begin(), end(), 0);
        std::cout << "Data @" << this << " is constructed" << std::endl;
    }

    ~Data()
    {
        std::cout << "Data @" << this << " is destructed" << std::endl;
    }

    const_iterator cbegin() const { return m_buffer; }
    const_iterator cend() const { return m_buffer+NELEM; }
    iterator begin() { return m_buffer; }
    iterator end() { return m_buffer+NELEM; }

    size_t size() const { return NELEM; }
    int   operator[](size_t it) const { return m_buffer[it]; }
    int & operator[](size_t it)       { return m_buffer[it]; }

    bool is_manipulated() const
    {
        for (size_t it=0; it < size(); ++it)
        {
            const int v = it;
            if ((*this)[it] != v) { return false; }
        }
        return true;
    }

private:

    // A lot of data that we don't want to reconstruct.
    int m_buffer[NELEM];

}; /* end class Data */

void manipulate_with_reference(Data & data)
{
    std::cout << "Manipulate with reference: " << &data << std::endl;

    for (size_t it=0; it < data.size(); ++it)
    {
        data[it] = it;
    }
    // In a real consumer function we will do much more meaningful operations.

    // However, we cannot destruct an object passed in with a reference.
}

static_assert(sizeof(Data *) * 2 == sizeof(std::shared_ptr<Data>), "shared_ptr should use two word");

std::shared_ptr<Data> worker1()
{
    // Create a new Data object.
    std::shared_ptr<Data> data;

#ifdef CTORNOWORK
    data = std::shared_ptr<Data>(new Data);
#endif

#ifdef MAKENOWORK
    data = std::make_shared<Data>();
#endif

    data = Data::make();

    std::cout << "worker 1 data.use_count(): " << data.use_count() << std::endl;

    // Manipulate the Data object.
    manipulate_with_reference(*data);

    return data;
}

void worker2(std::shared_ptr<Data> data)
{
    std::cout << "worker 2 data.use_count(): " << data.use_count() << std::endl;

    if (data->is_manipulated())
    {
        data.reset();
    }
    else
    {
        manipulate_with_reference(*data);
    }
}

int main(int, char **)
{
    std::shared_ptr<Data> data = worker1();
    std::cout << "Data pointer after worker 1: " << data.get() << std::endl;

    worker2(data);
    std::cout << "Data pointer after worker 2: " << data.get() << std::endl;

    data.reset();
    std::cout << "Data pointer after reset from outside: " << data.get() << std::endl;
    std::cout << "main data.use_count(): " << data.use_count() << std::endl;
}
$ g++ 01_fully.cpp -o 01_fully -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror
Build 01_fully.cpp with constructor error.#
$ g++ 01_fully.cpp -o 01_fully -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror -DCTORNOWORK
Build 01_fully.cpp with make_shared error.#
$ g++ 01_fully.cpp -o 01_fully -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror -DMAKENOWORK
Duplicate the ownership anywhere (02_duplicate.cpp).#
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <memory>

class Data
{

public:

    constexpr const static size_t NELEM = 1024*8;

    using iterator = int *;
    using const_iterator = const int *;

private:

    class ctor_passkey {};

public:

    Data() = delete;

    // TODO: Copyability and moveability should be considered, but we leave
    // them for now.

    static std::shared_ptr<Data> make()
    {
        std::shared_ptr<Data> ret = std::make_shared<Data>(ctor_passkey());
        return ret;
    }

    Data(ctor_passkey const &)
    {
        std::fill(begin(), end(), 0);
        std::cout << "Data @" << this << " is constructed" << std::endl;
    }

    ~Data()
    {
        std::cout << "Data @" << this << " is destructed" << std::endl;
    }

    Data * get_raw_ptr()
    {
        // Returning raw pointer discards the ownership management.
        return this;
    }

    std::shared_ptr<Data> get_shared_ptr()
    {
        // XXX: Recreate a shared_ptr will duplicate the reference counter, and
        // later results into double free.
        return std::shared_ptr<Data>(this);
    }

    const_iterator cbegin() const { return m_buffer; }
    const_iterator cend() const { return m_buffer+NELEM; }
    iterator begin() { return m_buffer; }
    iterator end() { return m_buffer+NELEM; }

    size_t size() const { return NELEM; }
    int   operator[](size_t it) const { return m_buffer[it]; }
    int & operator[](size_t it)       { return m_buffer[it]; }

    bool is_manipulated() const
    {
        for (size_t it=0; it < size(); ++it)
        {
            const int v = it;
            if ((*this)[it] != v) { return false; }
        }
        return true;
    }

private:

    // A lot of data that we don't want to reconstruct.
    int m_buffer[NELEM];

}; /* end class Data */

int main(int, char **)
{
    std::shared_ptr<Data> data = Data::make();
    std::cout << "data.use_count(): " << data.use_count() << std::endl;

    // This is the problematic call that creates an ill-formed shared pointer.
    std::shared_ptr<Data> holder2 = data->get_shared_ptr();
    std::cout << "a bad shared pointer is created" << std::endl;

    data.reset();
    std::cout << "data.use_count() after data.reset(): " << data.use_count() << std::endl;

    std::cout << "holder2.use_count(): " << holder2.use_count() << std::endl;
    holder2.reset();  // This line crashes with double free.
    // This line never gets reached since the above line causes double free and
    // crash.
    std::cout << "holder2.use_count() after holder2.reset(): " << holder2.use_count() << std::endl;
}
Build 02_duplicate.cpp (it contains the double-free bug).#
$ g++ 02_duplicate.cpp -o 02_duplicate -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror
Enable shared pointer from this (03_fromthis.cpp).#
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <memory>

class Data
  : public std::enable_shared_from_this<Data>
{

public:

    constexpr const static size_t NELEM = 1024*8;

    using iterator = int *;
    using const_iterator = const int *;

private:

    class ctor_passkey {};

public:

    Data() = delete;

    // TODO: Copyability and moveability should be considered, but we leave
    // them for now.

    static std::shared_ptr<Data> make()
    {
        std::shared_ptr<Data> ret = std::make_shared<Data>(ctor_passkey());
        return ret;
    }

    Data(ctor_passkey const &)
    {
        std::fill(begin(), end(), 0);
        std::cout << "Data @" << this << " is constructed" << std::endl;
    }

    ~Data()
    {
        std::cout << "Data @" << this << " is destructed" << std::endl;
    }

    std::shared_ptr<Data> get_shared_ptr()
    {
        // This is the right way to get the shared pointer from within the
        // object.
        return shared_from_this();
    }

    const_iterator cbegin() const { return m_buffer; }
    const_iterator cend() const { return m_buffer+NELEM; }
    iterator begin() { return m_buffer; }
    iterator end() { return m_buffer+NELEM; }

    size_t size() const { return NELEM; }
    int   operator[](size_t it) const { return m_buffer[it]; }
    int & operator[](size_t it)       { return m_buffer[it]; }

    bool is_manipulated() const
    {
        for (size_t it=0; it < size(); ++it)
        {
            const int v = it;
            if ((*this)[it] != v) { return false; }
        }
        return true;
    }

private:

    // A lot of data that we don't want to reconstruct.
    int m_buffer[NELEM];

}; /* end class Data */

int main(int, char **)
{
    std::shared_ptr<Data> data = Data::make();
    std::cout << "data.use_count(): " << data.use_count() << std::endl;

    std::shared_ptr<Data> holder2 = data->get_shared_ptr();
    std::cout << "data.use_count() after holder2: " << data.use_count() << std::endl;

    data.reset();
    std::cout << "data.use_count() after data.reset(): " << data.use_count() << std::endl;

    std::cout << "holder2.use_count() before holder2.reset(): " << holder2.use_count() << std::endl;
    holder2.reset();
    std::cout << "holder2.use_count() after holder2.reset(): " << holder2.use_count() << std::endl;
}
$ g++ 03_fromthis.cpp -o 03_fromthis -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror
The problem of circular reference of shared pointer (04_cyclic.cpp).#
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <memory>

class Child;

class Data
  : public std::enable_shared_from_this<Data>
{

public:

    constexpr const static size_t NELEM = 1024*8;

    using iterator = int *;
    using const_iterator = const int *;

private:

    class ctor_passkey {};

public:

    Data() = delete;

    // TODO: Copyability and moveability should be considered, but we leave
    // them for now.

    static std::shared_ptr<Data> make()
    {
        std::shared_ptr<Data> ret = std::make_shared<Data>(ctor_passkey());
        return ret;
    }

    Data(ctor_passkey const &)
    {
        std::fill(begin(), end(), 0);
        std::cout << "Data @" << this << " is constructed" << std::endl;
    }

    ~Data()
    {
        std::cout << "Data @" << this << " is destructed" << std::endl;
    }

    std::shared_ptr<Child>   child() const { return m_child; }
    std::shared_ptr<Child> & child()       { return m_child; }

    const_iterator cbegin() const { return m_buffer; }
    const_iterator cend() const { return m_buffer+NELEM; }
    iterator begin() { return m_buffer; }
    iterator end() { return m_buffer+NELEM; }

    size_t size() const { return NELEM; }
    int   operator[](size_t it) const { return m_buffer[it]; }
    int & operator[](size_t it)       { return m_buffer[it]; }

    bool is_manipulated() const
    {
        for (size_t it=0; it < size(); ++it)
        {
            const int v = it;
            if ((*this)[it] != v) { return false; }
        }
        return true;
    }

private:

    // A lot of data that we don't want to reconstruct.
    int m_buffer[NELEM];

    std::shared_ptr<Child> m_child;

}; /* end class Data */

class Child
  : public std::enable_shared_from_this<Child>
{

private:

    class ctor_passkey {};

public:

    Child() = delete;

    Child(std::shared_ptr<Data> const & data, ctor_passkey const &) : m_data(data) {}

    static std::shared_ptr<Child> make(std::shared_ptr<Data> const & data)
    {
        std::shared_ptr<Child> ret = std::make_shared<Child>(data, ctor_passkey());
        data->child() = ret;
        return ret;
    }

private:

    std::shared_ptr<Data> m_data;

}; /* end class Child */

int main(int, char **)
{
    std::shared_ptr<Data> data = Data::make();
    std::shared_ptr<Child> child = Child::make(data);
    std::cout << "data.use_count(): " << data.use_count() << std::endl;
    std::cout << "child.use_count(): " << child.use_count() << std::endl;

    std::weak_ptr<Data> wdata(data);
    std::weak_ptr<Child> wchild(child);

    data.reset();
    std::cout << "wdata.use_count() after data.reset(): " << wdata.use_count() << std::endl;
    std::cout << "wchild.use_count() after data.reset(): " << wchild.use_count() << std::endl;

    child.reset();
    std::cout << "wdata.use_count() after child.reset(): " << wdata.use_count() << std::endl;
    std::cout << "wchild.use_count() after child.reset(): " << wchild.use_count() << std::endl;
    // Oops, the reference count doesn't reduce to 0!

    return 0;
}
$ g++ 04_cyclic.cpp -o 04_cyclic -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror
Use weak pointers to break circular reference (05_weak.cpp).#
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <memory>

class Child;

class Data
  : public std::enable_shared_from_this<Data>
{

public:

    constexpr const static size_t NELEM = 1024*8;

    using iterator = int *;
    using const_iterator = const int *;

private:

    class ctor_passkey {};

public:

    Data() = delete;

    // TODO: Copyability and moveability should be considered, but we leave
    // them for now.

    static std::shared_ptr<Data> make()
    {
        std::shared_ptr<Data> ret = std::make_shared<Data>(ctor_passkey());
        return ret;
    }

    Data(ctor_passkey const &)
    {
        std::fill(begin(), end(), 0);
        std::cout << "Data @" << this << " is constructed" << std::endl;
    }

    ~Data()
    {
        std::cout << "Data @" << this << " is destructed" << std::endl;
    }

    std::shared_ptr<Child>   child() const { return m_child; }
    std::shared_ptr<Child> & child()       { return m_child; }

    const_iterator cbegin() const { return m_buffer; }
    const_iterator cend() const { return m_buffer+NELEM; }
    iterator begin() { return m_buffer; }
    iterator end() { return m_buffer+NELEM; }

    size_t size() const { return NELEM; }
    int   operator[](size_t it) const { return m_buffer[it]; }
    int & operator[](size_t it)       { return m_buffer[it]; }

    bool is_manipulated() const
    {
        for (size_t it=0; it < size(); ++it)
        {
            const int v = it;
            if ((*this)[it] != v) { return false; }
        }
        return true;
    }

private:

    // A lot of data that we don't want to reconstruct.
    int m_buffer[NELEM];

    std::shared_ptr<Child> m_child;

}; /* end class Data */

class Child
  : public std::enable_shared_from_this<Child>
{

private:

    class ctor_passkey {};

public:

    Child() = delete;

    Child(std::shared_ptr<Data> const & data, ctor_passkey const &) : m_data(data) {}

    static std::shared_ptr<Child> make(std::shared_ptr<Data> const & data)
    {
        std::shared_ptr<Child> ret = std::make_shared<Child>(data, ctor_passkey());
        data->child() = ret;
        return ret;
    }

private:

    // Replace shared_ptr with weak_ptr to Data.
    std::weak_ptr<Data> m_data;

}; /* end class Child */

int main(int, char **)
{
    std::shared_ptr<Data> data = Data::make();
    std::shared_ptr<Child> child = Child::make(data);
    std::cout << "data.use_count(): " << data.use_count() << std::endl;
    std::cout << "child.use_count(): " << child.use_count() << std::endl;

    std::weak_ptr<Data> wdata(data);
    std::weak_ptr<Child> wchild(child);

    child.reset();
    std::cout << "wdata.use_count() after child.reset(): " << wdata.use_count() << std::endl;
    std::cout << "wchild.use_count() after child.reset(): " << wchild.use_count() << std::endl;

    data.reset();
    std::cout << "wdata.use_count() after data.reset(): " << wdata.use_count() << std::endl;
    std::cout << "wchild.use_count() after data.reset(): " << wchild.use_count() << std::endl;
}
$ g++ 05_weak.cpp -o 05_weak -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror