std::make_shared导致了未定义的行为,但新的工作原理



考虑以下示例类:

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];
}
}
};

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

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.

然而,当我使用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

有什么想法吗?小提示:我被限制为不模板化Foo类,并且在板载数据阵列中确实有一个void* const

您对make_shared的调用为您的Foo类使用了一个复制构造函数,但您尚未定义该构造函数。因此,将使用默认的(编译器生成的(副本,并调用析构函数来删除临时副本。由于您的类没有正确实现"三条规则",这(可能(会导致未定义的行为。

之所以使用复制构造函数,是因为对std::make_shared的调用中的参数列表是Foo(3, false)–一个Foo对象,因此该调用只适合此cppreference页上列出的第一个重载。(请注意,没有类似于std::make_shared<T>(const T& src)过载的内容。(从该页面中,我们可以看到:

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

所以,用你的"args";对于Foo(3, false)make_shared实际上为要包装的对象调用了以下构造函数:

Foo(Foo(3, false))

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

std::shared_ptr<Foo> sp_foo = std::make_shared<Foo>(3, false);

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

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

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


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

最新更新