lo tolmencre
lo tolmencre

Reputation: 3934

Why is making a shared pointer from a reference copies the object?

I intended to have a constructor to accept references and then create pointers to the objects passed as references and store these pointers in a field. However, for some reason the way I did it, copies are being created, and I don't see why:

#include <iostream>
#include <vector>
#include <memory>

// the class with the field of pointers
template<class T, class... Types>
class C
{

private:
    std::vector<std::shared_ptr<T>> mem; // the field of pointers

public:
    C(T& t, Types&... args)
      // make pointers to the objects passed by reference and store them in mem
      : mem{ std::make_shared<T>(t), std::make_shared<T>(args)... }
    {

        // to demonstrate that the pointers point to copies, alter one of the objects (subscript operator expected)
        (*mem[0])[0] = 10;


        // demonstrate the effect on the local copies
        std::cout << "vectors in mem:" << "\n";
        for (const auto& x : mem) {
            for (const auto& y : *x) {
                std::cout << y << ' ';
            }
            std::cout << "\n";
        }
    }
};

int main()
{
    std::vector<int> v1{ 1, 2, 3 };
    std::vector<int> v2{ 1, 2, 3 };
    std::vector<int> v3{ 1, 2, 3 };

    // make an object of type C with some vectors to store pointers to in field mem
    C<std::vector<int>, std::vector<int>, std::vector<int>> c(v1, v2, v3);

    // demonstrate that original vectors are unaltered
    std::cout << "original vectors:"<< "\n";

    for (const auto& y : v1) {
        std::cout << y << ' ';
    }
    std::cout << "\n";

    for (const auto& y : v2) {
        std::cout << y << ' ';
    }
    std::cout << "\n";

    for (const auto& y : v3) {
        std::cout << y << ' ';
    }
    std::cout << "\n";
}

What am I missing here? Where is the copying happening and why?

Upvotes: 2

Views: 2199

Answers (3)

Mr.C64
Mr.C64

Reputation: 42984

std::make_shared<T> allocates a single chunk of memory large enough to store the object of type T (initializing it with the passed parameters, if any), and attached to it a control block to manage the shared ownership (like keeping the object reference count, etc.).

+---------------+
| Control block |
|               |
+ ............. +
|  Object (T)   |
|               |
+---------------+

So, even if you pass a reference to an object, make_shared will create its own "deep" copy (constructing its object copy in place in a section of the memory block allocated as described above).

Upvotes: 2

YSC
YSC

Reputation: 40100

What am I missing here? Where is the copying happening and why?

To answer the question literally, from cppreference.com:

template< class T, class... Args > shared_ptr<T> make_shared( Args&&... args ); Constructs an object of type T and wraps it in a std::shared_ptr using args as the parameter list for the constructor of T.

In your case, std::make_shared<T>(t) (t being a T&) invokes T::T(T const&) and builds a copy of t. An incorrect solution would be to create your std::shared_ptrs yourself without the help of std::make_shared ... but in this case you would create smart pointers to potentially statically allocated objects. And this is bad, like Undefined Behaviour bad.

A better solution is to simply avoid creating smart pointers to objects passed by reference, as it may come to a surprise to the caller. Better take smart pointers:

C(std::shared_ptr<T> t, std::shared_ptr<Types> ...args) { /* ... */ }

Upvotes: 2

The reason is std::make_shared<T>(t). make_shared will call the constructor of T that accepts the parameters it's being given. And you give it a T lvalue by reference.

Naturally the copy constructor is called. And it's a good thing. Had you created a shared pointer to the very object you are passed, your code will probably be a heap of undefined behavior.

I suggest you accept the parameter by smart pointer instead. It makes the ownership semantics clear to the user of your class from the API itself.

Upvotes: 2

Related Questions