为什么从引用创建共享指针会复制对象?

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

我打算有一个构造函数来接受引用,然后创建指向作为引用传递的对象的指针,并将这些指针存储在一个字段中。但是,出于某种原因,我这样做的方式正在创建副本,但我不明白为什么:

#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";
}

我在这里错过了什么?复制发生在哪里?为什么?

原因是std::make_shared<T>(t)make_shared 将调用 T 的构造函数,该构造函数接受给定的参数。你通过引用给它一个 T 左值。

自然是调用了拷贝构造函数。这是一件好事。如果您创建了一个共享指针 指向您传递的对象 ,您的代码可能会是一堆未定义的行为。

我建议您改用智能指针接受参数。它使 class 的用户从 API 本身清楚地了解所有权语义。

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

从字面上回答问题,来自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.

在你的例子中,std::make_shared<T>(t)t 是一个 T&)调用 T::T(T const&) 并构建 copy t。一个不正确的解决方案是在没有 std::make_shared 帮助的情况下自己创建 std::shared_ptr ……但在这种情况下,您将创建指向潜在静态分配对象的智能指针。这是不好的,就像未定义的行为不好。

更好的解决方案是简单地避免创建指向通过引用传递的对象的智能指针,因为这可能会让调用者感到意外。最好采取智能指针:

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

std::make_shared<T> 分配一个足够大的内存块来存储类型 T 的对象(使用传递的参数初始化它,如果有的话),并附加一个控制块来管理共享所有权(比如保持对象引用计数等)。

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

因此,即使您将 reference 传递给一个对象,make_shared 也会创建它自己的 "deep" copy(在如上所述分配的内存块的一部分中构建其对象副本)。