std::make_shared 导致未定义的行为,但新作品
std::make_shared leads to undefined behavior, but new works
考虑以下示例 class:
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 对象的共享指针时,我经常发现我初始化的数组数据显示未定义 behavior/becomes 已损坏。例如:
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
class 进行模板化,并且确实有一个 void* const
到板载数据数组。
您对 make_shared
的调用正在使用您尚未定义的 Foo
class 的复制构造函数。因此,将使用默认 (compiler-generated) 副本,并调用析构函数删除临时文件。由于您的 class 没有正确实现 Rule of Three,这(可能)导致未定义的行为。
正在使用复制构造函数,因为您对 std::make_shared
的调用中的参数列表是 Foo(3, false)
– 一个 Foo
对象,因此调用只适合 this cppreference page 上列出的第一个重载。 (请注意,没有什么类似于 std::make_shared<T>(const T& src)
重载。)从该页面,我们看到:
- 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.
因此,使用 Foo(3, false)
的“args”,make_shared
实际上会调用以下构造函数来包装对象:
Foo(Foo(3, false))
为了正确的行为,只需将 3
和 false
作为参数传递给 make_shared
:
std::shared_ptr<Foo> sp_foo = std::make_shared<Foo>(3, false);
您可以 证明 原始代码中的错误,方法是向记录一些输出的 Foo
class 添加析构函数,例如:~Foo() { std::cout << "Destroying...\n"; }
.您将在执行对 make_shared
.
的调用后看到该输出
您可以通过删除 Foo
复制构造函数 防止 这种意外 error/oversight:Foo(const Foo& f) = delete;
。这将生成如下所示的编译器错误:
error : call to deleted constructor of 'Foo'
但是,在第二种情况下,您使用 std::shared_ptr
constructor(链接页面上显示的第三种形式)。这没有问题,因为构造函数 'just' 将给定的指针包装到托管对象中,不需要复制或销毁。
考虑以下示例 class:
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 对象的共享指针时,我经常发现我初始化的数组数据显示未定义 behavior/becomes 已损坏。例如:
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
class 进行模板化,并且确实有一个 void* const
到板载数据数组。
您对 make_shared
的调用正在使用您尚未定义的 Foo
class 的复制构造函数。因此,将使用默认 (compiler-generated) 副本,并调用析构函数删除临时文件。由于您的 class 没有正确实现 Rule of Three,这(可能)导致未定义的行为。
正在使用复制构造函数,因为您对 std::make_shared
的调用中的参数列表是 Foo(3, false)
– 一个 Foo
对象,因此调用只适合 this cppreference page 上列出的第一个重载。 (请注意,没有什么类似于 std::make_shared<T>(const T& src)
重载。)从该页面,我们看到:
- 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.
因此,使用 Foo(3, false)
的“args”,make_shared
实际上会调用以下构造函数来包装对象:
Foo(Foo(3, false))
为了正确的行为,只需将 3
和 false
作为参数传递给 make_shared
:
std::shared_ptr<Foo> sp_foo = std::make_shared<Foo>(3, false);
您可以 证明 原始代码中的错误,方法是向记录一些输出的 Foo
class 添加析构函数,例如:~Foo() { std::cout << "Destroying...\n"; }
.您将在执行对 make_shared
.
您可以通过删除 Foo
复制构造函数 防止 这种意外 error/oversight:Foo(const Foo& f) = delete;
。这将生成如下所示的编译器错误:
error : call to deleted constructor of 'Foo'
但是,在第二种情况下,您使用 std::shared_ptr
constructor(链接页面上显示的第三种形式)。这没有问题,因为构造函数 'just' 将给定的指针包装到托管对象中,不需要复制或销毁。