Skip to main content
GameDev.net gamedev.net
🔒 Locked

std::make_shared vs. new

Started by matt77hias Mar 15, 2018 at 10:48 AM 3 replies 14.9k views
Original Post
matt77hias
matt77hias

When you use std::make_shared, the control and the data block of the std::shared_ptr will be allocated together using a single allocation. Since, the control block needs to stay alive as long as there are std::shared_ptrs and std::weak_ptrs to the same data, the data block must stay alive as well and can thus not be destructed straight away. When you use new instead, the control and the data block will be allocated separately. This implies that the data block can be destructed when there are no std::shared_ptrs to the same data (independent of the existence of std::weak_ptrs to the same data). (Cfr. https://stackoverflow.com/a/18301738/1731200)

So far the theory ;P . I tried to see this in practice using a small code sample. But somehow the data block is always destructed before resetting the std::weak_ptrs to the same data for both GCC and Clang. Is this behavior just compiler implementation dependent?


#include <memory>
#include <iostream>

struct Widget {
    ~Widget() {
        std::cout << "Widget::~Widget()" << std::endl;
    }
    int data;
};

void test(bool use_make_shared) {
    std::shared_ptr< Widget > sp;
    if (use_make_shared) {
        sp = std::make_shared< Widget >();
    } else {
        sp = std::shared_ptr< Widget >(new Widget());
    }
    std::weak_ptr< Widget > wp(sp);
    
    sp.reset();
    std::cout << "No std::shared_ptr's anymore." << std::endl;
    wp.reset();
    std::cout << "No std::weak_ptr's anymore." << std::endl;
}

int main() { 
    test(true);
    std::cout << std::endl;
    test(false);
};

Widget::~Widget()
No std::shared_ptr's anymore.
No std::weak_ptr's anymore.

Widget::~Widget()
No std::shared_ptr's anymore.
No std::weak_ptr's anymore.
🧙
matt77hias
matt77hias
57 minutes ago, Bregma said:

You seem a little confused by what a weak_ptr is and how it relates to a shared_ptr?

This is just how you would use a std::weak_ptr, by checking expiration and if not expired creating a temporary std::shared_ptr to do something useful. So not really related...

Documentation:

In a typical implementation, std::shared_ptr holds only two pointers:

  • the stored pointer (one returned by get());
  • a pointer to control block.

The control block is a dynamically-allocated object that holds:

  • either a pointer to the managed object or the managed object itself;
  • the deleter (type-erased);
  • the allocator (type-erased);
  • the number of shared_ptrs that own the managed object;
  • the number of weak_ptrs that refer to the managed object.

When shared_ptr is created by calling std::make_shared or std::allocate_shared, the memory for both the control block and the managed object is created with a single allocation. The managed object is constructed in-place in a data member of the control block. When shared_ptr is created via one of the shared_ptr constructors, the managed object and the control block must be allocated separately. In this case, the control block stores a pointer to the managed object.

The pointer held by the shared_ptr directly is the one returned by get(), while the pointer/object held by the control block is the one that will be deleted when the number of shared owners reaches zero. These pointers are not necessarily equal.

The destructor of shared_ptr decrements the number of shared owners of the control block. If that counter reaches zero, the control block calls the destructor of the managed object. The control block does not deallocate itself until the std::weak_ptr counter reaches zero as well.

In practical implementations, the number of weak pointers may be incremented if there is a shared pointer to the same control block.

Edit:

Ah now I see my mistake. The destructor will of course be called, but deallocation will not necessary occur at the same time.

In case of "new", deallocation + destruction will both occur.

In case of "make_shared", destruction will occur, but deallocation is postponed till the control block can be deallocated which will happen if both the shared_ptr and weak_ptr counts reach zero.

🧙
matt77hias
matt77hias
9 minutes ago, matt77hias said:

Edit:

Ah now I see my mistake. The destructor will of course be called, but deallocation will not necessary occur at the same time.

In case of "new", deallocation + destruction will both occur.

In case of "make_shared", destruction will occur, but deallocation is postponed till the control block can be deallocated which will happen if both the shared_ptr and weak_ptr counts reach zero.

As expected :P


#include <memory>
#include <iostream>

bool logging = false;

void* operator new(std::size_t size) {
    auto ptr = std::malloc(size);
    if (logging) {
        std::cout << "Allocated: " << (uintptr_t)ptr << std::endl;
    }
    return ptr;
}
void operator delete(void* ptr) noexcept {
    std::free(ptr);
    if (logging) {
       std::cout << "Deallocated: " << (uintptr_t)ptr << std::endl; 
    }
}

struct Widget {
    ~Widget() {
        std::cout << "Widget::~Widget()" << std::endl;
    }
    int data;
};

void test(bool use_make_shared) {
    std::shared_ptr< Widget > sp;
    if (use_make_shared) {
        sp = std::make_shared< Widget >();
    } else {
        sp = std::shared_ptr< Widget >(new Widget());
    }
    std::weak_ptr< Widget > wp(sp);
    
    sp.reset();
    std::cout << "No std::shared_ptr's anymore." << std::endl;
    wp.reset();
    std::cout << "No std::weak_ptr's anymore." << std::endl;
}

int main() { 
    logging = true;
    test(true);
    std::cout << std::endl;
    test(false);
    logging = false;
};

Allocated: 12043760
Widget::~Widget()
No std::shared_ptr's anymore.
Deallocated: 12043760
No std::weak_ptr's anymore.

Allocated: 12043376
Allocated: 12043760
Widget::~Widget()
Deallocated: 12043376
No std::shared_ptr's anymore.
Deallocated: 12043760
No std::weak_ptr's anymore.
🧙

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.