根据 C++ 标准,显式调用构造函数和析构函数是否安全?
Is explicitly calling constructors and destructors safe, according to the C++ standard?
一些开发人员明确调用构造函数和析构函数来解决一些问题。我知道,这不是一个好的做法,但似乎是为了实现一些场景。
例如这篇文章Beautiful Native Libraries,作者就使用了这种技术。
在下面的代码中,最后可以看到显式调用了构造函数:
#include <limits>
template <class T>
struct proxy_allocator {
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T *pointer;
typedef const T *const_pointer;
typedef T& reference;
typedef const T &const_reference;
typedef T value_type;
template <class U>
struct rebind {
typedef proxy_allocator<U> other;
};
proxy_allocator() throw() {}
proxy_allocator(const proxy_allocator &) throw() {}
template <class U>
proxy_allocator(const proxy_allocator<U> &) throw() {}
~proxy_allocator() throw() {}
pointer address(reference x) const { return &x; }
const_pointer address(const_reference x) const { return &x; }
pointer allocate(size_type s, void const * = 0) {
return s ? reinterpret_cast<pointer>(yl_malloc(s * sizeof(T))) : 0;
}
void deallocate(pointer p, size_type) {
yl_free(p);
}
size_type max_size() const throw() {
return std::numeric_limits<size_t>::max() / sizeof(T);
}
void construct(pointer p, const T& val) {
new (reinterpret_cast<void *>(p)) T(val);
}
void destroy(pointer p) {
p->~T();
}
bool operator==(const proxy_allocator<T> &other) const {
return true;
}
bool operator!=(const proxy_allocator<T> &other) const {
return false;
}
};
对于像这样的某些场景,可能需要显式调用构造函数和析构函数,但标准是怎么说的:它是未定义的行为,是未指定的行为,是实现定义的行为,还是定义明确的行为?
是的,它受支持且定义明确,很安全。
new (reinterpret_cast<void *>(p)) T(val);
称为placement new syntax and is used to construct an object at a specific memory location,默认行为;如发布的分配器中要求的那样。如果 placement new 为特定类型重载 T
,则将调用它而不是全局 placement new。
销毁此类构造对象的唯一方法是 explicitly call the destructor p->~T();
.
使用 new 放置和显式销毁 require/allow 实现的代码控制对象的生命周期 - 编译器在这种情况下提供的帮助很少;因此,重要的是将对象构建在对齐良好且分配充分的位置。它们的使用经常在分配器中找到,例如在 OP 和 std::allocator
.
中
是的,它是完全安全的。事实上,所有 标准容器,如 std::vector
默认都使用技术 ,因为这是将内存分配与元素构造分开的唯一方法。
更准确地说,标准容器模板有一个 Allocator
模板参数,默认为 std::allocator
, and std::allocator
uses placement new in its allocate
成员函数。
例如,这是允许 std::vector
实现 push_back
的原因,这样内存分配就不必一直发生,而是在当前容量达到时分配一些额外的内存已经不够了,为添加了 future push_back
s.
的元素准备 space
这意味着当你在循环中调用 push_back
一百次时,std::vector
实际上足够聪明,不会每次都分配内存,这有助于提高性能,因为重新分配和移动现有容器内容到一个新的内存位置是昂贵的。
示例:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> v;
std::cout << "initial capacity: " << v.capacity() << "\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(0);
std::cout << "capacity after " << (i + 1) << " push_back()s: "
<< v.capacity() << "\n";
}
}
输出:
initial capacity: 0
capacity after 1 push_back()s: 1
capacity after 2 push_back()s: 2
capacity after 3 push_back()s: 3
capacity after 4 push_back()s: 4
capacity after 5 push_back()s: 6
capacity after 6 push_back()s: 6
capacity after 7 push_back()s: 9
capacity after 8 push_back()s: 9
capacity after 9 push_back()s: 9
capacity after 10 push_back()s: 13
capacity after 11 push_back()s: 13
capacity after 12 push_back()s: 13
capacity after 13 push_back()s: 13
capacity after 14 push_back()s: 19
(...)
capacity after 94 push_back()s: 94
capacity after 95 push_back()s: 141
capacity after 96 push_back()s: 141
capacity after 97 push_back()s: 141
capacity after 98 push_back()s: 141
capacity after 99 push_back()s: 141
capacity after 100 push_back()s: 141
当然,您 不想为 潜在的未来元素 调用构造函数。对于 int
没关系,但我们需要针对每个 T
的解决方案,包括没有默认构造函数的类型。这就是 placement new 的强大之处:首先分配内存,然后使用手动构造函数调用将元素放入分配的内存中。
附带说明一下,new[]
不可能实现所有这些。事实上,new[]
是一个非常无用的语言功能。
P.S.: 仅仅因为标准容器在内部使用 placement new,这并不意味着您应该在自己的代码中使用它。它 是 一种低级技术,如果您不实现自己的通用数据结构,因为没有标准容器提供您需要的功能,您可能永远找不到它的任何用处.
一些开发人员明确调用构造函数和析构函数来解决一些问题。我知道,这不是一个好的做法,但似乎是为了实现一些场景。
例如这篇文章Beautiful Native Libraries,作者就使用了这种技术。
在下面的代码中,最后可以看到显式调用了构造函数:
#include <limits>
template <class T>
struct proxy_allocator {
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T *pointer;
typedef const T *const_pointer;
typedef T& reference;
typedef const T &const_reference;
typedef T value_type;
template <class U>
struct rebind {
typedef proxy_allocator<U> other;
};
proxy_allocator() throw() {}
proxy_allocator(const proxy_allocator &) throw() {}
template <class U>
proxy_allocator(const proxy_allocator<U> &) throw() {}
~proxy_allocator() throw() {}
pointer address(reference x) const { return &x; }
const_pointer address(const_reference x) const { return &x; }
pointer allocate(size_type s, void const * = 0) {
return s ? reinterpret_cast<pointer>(yl_malloc(s * sizeof(T))) : 0;
}
void deallocate(pointer p, size_type) {
yl_free(p);
}
size_type max_size() const throw() {
return std::numeric_limits<size_t>::max() / sizeof(T);
}
void construct(pointer p, const T& val) {
new (reinterpret_cast<void *>(p)) T(val);
}
void destroy(pointer p) {
p->~T();
}
bool operator==(const proxy_allocator<T> &other) const {
return true;
}
bool operator!=(const proxy_allocator<T> &other) const {
return false;
}
};
对于像这样的某些场景,可能需要显式调用构造函数和析构函数,但标准是怎么说的:它是未定义的行为,是未指定的行为,是实现定义的行为,还是定义明确的行为?
是的,它受支持且定义明确,很安全。
new (reinterpret_cast<void *>(p)) T(val);
称为placement new syntax and is used to construct an object at a specific memory location,默认行为;如发布的分配器中要求的那样。如果 placement new 为特定类型重载 T
,则将调用它而不是全局 placement new。
销毁此类构造对象的唯一方法是 explicitly call the destructor p->~T();
.
使用 new 放置和显式销毁 require/allow 实现的代码控制对象的生命周期 - 编译器在这种情况下提供的帮助很少;因此,重要的是将对象构建在对齐良好且分配充分的位置。它们的使用经常在分配器中找到,例如在 OP 和 std::allocator
.
是的,它是完全安全的。事实上,所有 标准容器,如 std::vector
默认都使用技术 ,因为这是将内存分配与元素构造分开的唯一方法。
更准确地说,标准容器模板有一个 Allocator
模板参数,默认为 std::allocator
, and std::allocator
uses placement new in its allocate
成员函数。
例如,这是允许 std::vector
实现 push_back
的原因,这样内存分配就不必一直发生,而是在当前容量达到时分配一些额外的内存已经不够了,为添加了 future push_back
s.
这意味着当你在循环中调用 push_back
一百次时,std::vector
实际上足够聪明,不会每次都分配内存,这有助于提高性能,因为重新分配和移动现有容器内容到一个新的内存位置是昂贵的。
示例:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> v;
std::cout << "initial capacity: " << v.capacity() << "\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(0);
std::cout << "capacity after " << (i + 1) << " push_back()s: "
<< v.capacity() << "\n";
}
}
输出:
initial capacity: 0
capacity after 1 push_back()s: 1
capacity after 2 push_back()s: 2
capacity after 3 push_back()s: 3
capacity after 4 push_back()s: 4
capacity after 5 push_back()s: 6
capacity after 6 push_back()s: 6
capacity after 7 push_back()s: 9
capacity after 8 push_back()s: 9
capacity after 9 push_back()s: 9
capacity after 10 push_back()s: 13
capacity after 11 push_back()s: 13
capacity after 12 push_back()s: 13
capacity after 13 push_back()s: 13
capacity after 14 push_back()s: 19
(...)
capacity after 94 push_back()s: 94
capacity after 95 push_back()s: 141
capacity after 96 push_back()s: 141
capacity after 97 push_back()s: 141
capacity after 98 push_back()s: 141
capacity after 99 push_back()s: 141
capacity after 100 push_back()s: 141
当然,您 不想为 潜在的未来元素 调用构造函数。对于 int
没关系,但我们需要针对每个 T
的解决方案,包括没有默认构造函数的类型。这就是 placement new 的强大之处:首先分配内存,然后使用手动构造函数调用将元素放入分配的内存中。
附带说明一下,new[]
不可能实现所有这些。事实上,new[]
是一个非常无用的语言功能。
P.S.: 仅仅因为标准容器在内部使用 placement new,这并不意味着您应该在自己的代码中使用它。它 是 一种低级技术,如果您不实现自己的通用数据结构,因为没有标准容器提供您需要的功能,您可能永远找不到它的任何用处.