c++c++14shared-ptrsmart-pointersvalue-initialization

Initialise std::shared_ptr using value initialisation


I was experimenting with braced initialisation and specifically using value initialisation.

To my understanding int x {} uses value initialisation to set the value of x to 0.

So I then tried this with a std::shared_ptr and a trivial inner class Foo: std::shared_ptr<Foo> sharedFoo {} and tried to call a function from sharedFoo. I was slightly surprised to see this resulted in a segmentation fault.

My intuition had made me think that this would be value initialised, using the default constructor for Foo. However, I then realised that sharedFoo has not actually had any memory allocated to it, using either new Foo or std::make_shared. Is my reasoning correct here. Is my reasoning correct here? I was hoping to default initialise a shared_ptr using simple braces.

My full code for reference is below:

    #include <memory>
    #include <iostream>
    
    class Foo
    {
    public:
        int getValue() const { return value; }
    private:
        int value = 0;
    };
    
    int main()
    {
        int i {};
    
        std::shared_ptr<Foo> sharedFoo { };
    
        std::cout << i << std::endl;
    
        std::cout << sharedFoo->getValue() << std::endl; //SEGFAULT
    
        return 0;
    }

For reference, my question is not the following: std::shared_ptr initialization: make_shared<Foo>() vs shared_ptr<T>(new Foo)

Really, the core of the question is why int i {} performs a value initialisation of i, but std::shared_ptr<Foo> sharedFoo {} does a default initialisation of sharedFoo and doesn't value initialise the contained pointer.


Solution

  • To my understanding int x {} uses value initialisation to set the value of x to 0.

    Correct!

    Really, the core of the question is why int i {} performs a value initialisation of i, but std::shared_ptr<Foo> sharedFoo {} does a default initialisation of sharedFoo

    Well, that's exactly what you asked it to do. Value initialization does different things for scalars (like int) and class objects with constructors.

    This is because scalars have a well-defined zero value, whereas class types at most have a well-defined default constructor, which may have to establish some invariants before the object can be used.

    If a class has only non-trivial constructors, you can't use an object of that class at all until the constructor has completed because the object formally doesn't exist. There's no reasonable "zero value" for such classes.

    and doesn't value initialise the contained pointer.

    But it does. It does exactly that. Value initializing a pointer sets it to nullptr. You can't dereference that either.

    My intuition had made me think that this would be value initialised, using the default constructor for Foo

    But you haven't declared any object of type Foo (let alone initialized it). You declared a (smart) pointer to Foo, and that's what you value-initialized.

    You can, if you wish, write a smart pointer class that always creates an object of the contained type. But that isn't what shared_ptr does.