std::make_shared 导致未定义的行为,但新的作品

Huf*_*294 1 c++ memory shared-ptr

考虑以下示例类:

class Foo {

public:
    void* const arr_;

    Foo() = delete;

    Foo(const size_t size, bool high_precision) :
        arr_(Initialize(size, high_precision)) {};

    template <typename T>
    T* GetDataPointer() {
        return (T* const)arr_;
    }
private:
    static void* Initialize(const size_t size, bool high_prec) {
        if (high_prec) {
            return new double[size];
        }
        else {
            return new float[size];
        }
    }

};
Run Code Online (Sandbox Code Playgroud)

当我使用 创建指向 Foo 对象的共享指针时std::make_shared,我经常发现我初始化的数组数据显示未定义的行为/变得损坏。例如:

std::shared_ptr<Foo> sp_foo = std::make_shared<Foo>(Foo(3,false));
    auto foo_data = sp_foo->GetDataPointer<float>();
    foo_data[0] = 1.1;
    foo_data[1] = 2.2;
    foo_data[2] = 3.3;
    std::cout << "First Value: " << 
        sp_foo->GetDataPointer<float>()[0]; // Sometimes this is 1.1, sometimes it is meaningless.
Run Code Online (Sandbox Code Playgroud)

但是,当我使用 初始化共享指针时new,这个问题似乎就消失了。

std::shared_ptr<Foo> sp_foo (new Foo(3,false));
    auto foo_data = sp_foo->GetDataPointer<float>();
    foo_data[0] = 1.1;
    foo_data[1] = 2.2;
    foo_data[2] = 3.3;
    std::cout << "First Value: " << 
        sp_foo->GetDataPointer<float>()[0]; // Correctly prints 1.1
Run Code Online (Sandbox Code Playgroud)

对正在发生的事情有什么想法吗?小旁注:我被迫不模板化类Foo,并且确实有一个void* const板载数据数组。

Adr*_*ica 5

您的调用正在为您的类使用您尚未定义的make_shared复制构造函数。Foo因此,将使用默认的(编译器生成的)副本,并且将调用析构函数来删除临时文件。由于您的类没有正确实现“三规则”,这(可能)会导致未定义的行为。

\n

使用复制构造函数是因为调用中的参数列表std::make_sharedFoo(3, false)\xe2\x80\x93Foo对象,因此该调用仅适合此 cppreference page上列出的第一个重载。(请注意,没有任何类似于std::make_shared<T>(const T& src)重载的内容。)从该页面中,我们看到:

\n
\n
    \n
  1. 构造一个类型 T 的对象并将其包装在 std::shared_ptr\n中,使用 args 作为 T 构造函数的参数列表。
  2. \n
\n
\n

因此,使用您的“args” Foo(3, false)make_shared实际上会调用以下构造函数来包装对象:

\n
Foo(Foo(3, false))\n
Run Code Online (Sandbox Code Playgroud)\n

为了正确的行为,只需将3false作为参数传递给make_shared

\n
std::shared_ptr<Foo> sp_foo = std::make_shared<Foo>(3, false);\n
Run Code Online (Sandbox Code Playgroud)\n

您可以通过向记录某些输出的类添加析构函数来演示原始代码中的错误Foo,例如:~Foo() { std::cout << "Destroying...\\n"; }。执行调用后您将看到该输出make_shared

\n

您可以通过删除复制构造函数来防止这种意外错误/疏忽FooFoo(const Foo& f) = delete;。这将生成如下编译器错误:

\n
\n

错误:调用已删除的“Foo”构造函数

\n
\n
\n

但是,在第二种情况下,您使用std::shared_ptr构造函数(链接页面上显示的第三种形式)。这没有问题,因为构造函数“只是”将给定的指针包装到托管对象中,不需要复制或销毁。

\n