1

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.

HarryP2023
  • 298
  • 1
  • 13
  • x86-64 gcc trunk. I've also updated the question to remove `new Foo` – HarryP2023 Jul 11 '23 at 14:01
  • Ok, that makes it clear. Thanks. – Ted Lyngmo Jul 11 '23 at 14:04
  • Does this answer your question? [std::shared\_ptr initialization: make\_shared() vs shared\_ptr(new Foo)](https://stackoverflow.com/questions/18301511/stdshared-ptr-initialization-make-sharedfoo-vs-shared-ptrtnew-foo) The question is a bit different, but the answer is applicable here as well. – zerocukor287 Jul 11 '23 at 14:08
  • @zerocukor287, no sorry that question is about the difference between std::make_shared and std::shared_ptr(new Foo). My question is about value initialisation of a std::shared_ptr – HarryP2023 Jul 11 '23 at 14:09
  • 3
    The core of your confusion seems to be that a `shared_ptr` (or even a raw pointer) _is_ an object in its own right. It is not the object it points to (if any). If you value-initialized a raw pointer that would also be `nullptr`. – Useless Jul 11 '23 at 14:16
  • @Useless value initialisation of a raw pointer being `nullptr` makes sense to me. But shared_ptr is just a thin wrapper around around a raw pointer, correct? So to me, it sounds like that is an object in its own right (and yes, not the object it points to, I understand that) – HarryP2023 Jul 11 '23 at 14:19
  • A raw pointer is an object, with a value. A `shared_ptr` is an object with a value _and constructors and behaviour and invariants_, but that doesn't change it from being pointer-like to somehow not existing at all and just being replaced by the object it points to. The value-initialized pointer doesn't point to anything, The value-initialized `shared_ptr` doesn't point to anything either, because that's how its default constructor is required to behave. – Useless Jul 11 '23 at 14:26
  • Yes, the `shared_ptr` is an object in its own right and being a (semi) thin wrapper, you could see it as-if it's the pointer inside it that you initialize. `Foo* ptr{};` - if that makes sense. It's not a perfect analogy but for the usecase you have it works. – Ted Lyngmo Jul 11 '23 at 14:26

3 Answers3

2
std::shared_ptr<Foo> sharedFoo{};

sharedFoo is a default constructed, empty, shared_ptr and dereferencing it has undefined behavior. It does not own an object.

It's the same as-if you'd done:

std::shared_ptr<Foo> sharedFoo;
sharedFoo->getValue();

What you want is likely:

auto sharedFoo = std::make_shared<Foo>();
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • I was expecting it to value construct a `Foo` object, like `int i {}` value constructs i to be equal to zero. Is this an exception to the rule or simply not the behaviour I should expect? – HarryP2023 Jul 11 '23 at 14:04
  • 1
    @HarryP2023 It's simply a default constructed (empty) `shared_ptr`, not a `shared_ptr` with a default constructed `Foo` – Ted Lyngmo Jul 11 '23 at 14:06
  • Ted Lyngmo thank you, so `std::shared_ptr sharedFoo {}` performs a default construction whereas `int i {}` performs a value construction? – HarryP2023 Jul 11 '23 at 14:07
  • 1
    @HarryP2023 Yes, the `shared_ptr` is default-initialized, see: [value initialization](https://en.cppreference.com/w/cpp/language/value_initialization) – Ted Lyngmo Jul 11 '23 at 14:12
  • 3
    Well, [value initialization](https://en.cppreference.com/w/cpp/language/value_initialization) of an object with a default constructor _is_ default initialization (ie, the constructor is used). Value initialization of `int` or other scalars _is_ zero initialization. It's all listed. – Useless Jul 11 '23 at 14:14
  • Okay I see. So `std::shared_ptr(new Foo)` is value initialised? – HarryP2023 Jul 11 '23 at 14:15
  • No. You can read the documentation on [`initialization`](https://en.cppreference.com/w/cpp/language/initialization) instead of guessing: that would be _direct initialization_ (because you're explicitly passing a constructor argument) – Useless Jul 11 '23 at 14:19
  • The `Foo` is default-initialized by the `new` expression (you didn't give it any constructor arguments or a braced init list or anything) - which has the same effect as value-initializing would have in this case. – Useless Jul 11 '23 at 14:21
  • 2
    @HarryP2023 now matter how the constructor is invoked, its just a constructor, and the default constructor of the smart pointer does not create a `Foo`, because thats not what it is meant to be used for. It is meant to create an emtpy smart pointer. – 463035818_is_not_an_ai Jul 11 '23 at 14:36
  • 1
    @HarryP2023 • a `std::shared_ptr` models a **pointer** (a smart pointer, with federated ownership), not a **Foo**. – Eljay Jul 11 '23 at 14:49
2

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.

Useless
  • 64,155
  • 6
  • 88
  • 132
2

The following two variable definitions

int i {};
std::shared_ptr<Foo> sharedFoo { };

both perform value-initialization.

It is also instructive to compare with:

Foo* p {};

In this case, p is initialized to a null pointer, because value-initialization for a pointer type always initializes it to a null pointer. It is unclear why you would expect value-initialization for a pointer to create an object for the pointer to point to.

Value-initialization of std::shared_ptr<Foo> calls the default constructor, and the result is that the std::shared_ptr<Foo> object is placed in a state that is logically similar to the value-initialized pointer state, i.e., null.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312