0

Consider the following snippet:

#include <memory>
#include <typeinfo>
#include <iostream>


class Widget {
};

int main() {
    auto shared_ptr_to_widget = std::shared_ptr<Widget>({});
    std::cout << "type of shared_ptr_to_widget: " << typeid(shared_ptr_to_widget).name() << std::endl;
    auto maybe_a_widget = *shared_ptr_to_widget;
    std::cout << "type of maybe_a_widget: " << typeid(maybe_a_widget).name() << std::endl;
}

This will output:

> type of shared_ptr_to_widget: St10shared_ptrI6WidgetE
> type of maybe_a_widget: 6Widget

However, if I replace the Widget class with:

class Widget {
public:
    Widget(): a{1}{}
    int a;
};

Then it seg faults at the following line:

auto maybe_a_widget = *shared_ptr_to_widget;

I understand if I actually wanted to make a shared pointer to an instance of an object I should use

std::make_shared<Widget>()

and this would actually call the constructor of Widget and everything would be fine and dandy. But I'd really like to understand what is going on here and why the behaviour changes based on the constructor of the Widget class.

I've tried looking at the constructors for shared_ptr http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr but I get a bit lost. I imagine it's my lack of understanding in what is actually happening when using an empty initializer list as a parameter to shared_ptr<>().

1 Answer 1

2

Your code has undefined behaviour, since *shared_ptr_to_widget dereferences a null pointer, since you only default-constructed shared_ptr_to_widget, which results in an empty shared pointer that doesn't own anything.

At the point where the program execution has undefined behaviour, the C++ standard imposes no constraints on the behaviour of a conforming implementation, so anything can happen. You are observing a particular instance of "anything".

To create a pointer that owns a widget, say either

auto shared_ptr_to_widget = std::shared_ptr<Widget>(new Widget);  // bad

or

auto shared_ptr_to_widget = std::make_shared<Widget>();  // good
Sign up to request clarification or add additional context in comments.

4 Comments

How does the compiler make it past that stage in the first implementation of Widget? It should be a null pointer to to a class of type Widget in both scenarios?
@BrockHargreaves: I don't know, ask your compiler (i.e. look at the machine code)! There's generally very little value in debating the particulars of undefined behaviour. Anything can happen, and which flavour of "anything" you're seeing is basically irrelevant. If pressed I'd say that because the original widget is trival and empty, no code need to be generated to initialize it (all values of the type are equal), and so the absence of a value went unnoticed.
Yeah, it's no surprise that if you also output the use count of the shared pointer it is zero. I wish it would seg fault in both scenarios though. Thanks for your answer.
When you say "you only default-constructed shared_ptr_to_widget", do you mean that (1) in auto shared_ptr_to_widget = std::shared_ptr<Widget>({}); this is a direct initialization calling the constructor explicitly with 1 parameter, which is an empty list, (2) then the empty list {} is a value initialization which triggered a default initialization?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.