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;
}
|
Build 01_raw_pointer.cpp.#
$ 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;
}
|
Build 02_reference.cpp.#
$ 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
}
|
Build 03_ownership.cpp.#
$ 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;
}
|
Build 04_unique.cpp.#
$ 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
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;
}
|
Build 01_fully.cpp.#
$ 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;
}
|
Build 03_fromthis.cpp.#
$ 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;
}
|
Build 04_cyclic.cpp.#
$ 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;
}
|
Build 05_weak.cpp.#
$ g++ 05_weak.cpp -o 05_weak -std=c++17 -g -O3 -m64 -Wall -Wextra -Werror