如何高效地克隆动态分配的阵列
How can I efficiently clone a dynamically allocated array?
我有一个类,它是一个模板化的智能指针,用于包装动态分配的数组。我知道 STL 中有一些类可以用于此目的,尤其是在 C++11 中,但这是一个广泛使用的内部类。
我希望为它编写一个 Clone(( 方法。我最初的实现使用了 std::copy,但我意识到在分配数组时我应该能够避免默认构造。
我对 PoC 的尝试最终出现分段错误:
#include <iostream>
#include <algorithm>
class A
{
public:
A(int j) : i(j) {}
~A() {std::cout << "Destroying " << i << std::endl;}
private:
int i;
};
int main()
{
int a[] = {1, 2, 3};
A* arr = static_cast<A*>(::operator new[](sizeof(A) * 3));
std::uninitialized_copy(a, a + 3, arr);
delete [] arr;//::operator delete[](arr);
}
如何创建一个动态分配的 T 数组,使用 std::uninitialized_copy 初始化,以便可以使用"delete []"将其删除(即被视为使用简单的"新 T[N]"分配(?
由于人们似乎难以理解我在问什么,这是我问题的本质:
#include <algorithm>
template <typename T>
T* CloneArray(T* in_array, size_t in_count)
{
if (!in_array)
return nullptr;
T* copy = new T[in_count];
try
{
std::copy(in_array, in_array + in_count, copy);
}
catch (...)
{
delete [] copy;
throw;
}
return copy;
}
我将如何重写此函数以防止调用T::T()
(如果它甚至存在!(,同时返回完全相同的结果(假设我们的类型在该T t; t = other;
中表现良好,并且T t(other);
是等效的(,包括可以使用标准delete []
运算符删除函数的结果。
如何创建一个动态分配的 T 数组,使用 std::uninitialized_copy 初始化,以便可以使用"delete []"将其删除(即被视为使用简单的"新 T[N]"分配(?
因此,鉴于相对简单的要求,即 内存可以删除 delete[]
,让我们看看我们有什么选择。
注意:标准中的所有引用都来自 C++14 草案 N3797,我不是最擅长标准解释的人,所以请持保留态度。
混合malloc()/free()
和new[]/delete[]
未定义,因为 new 不一定调用 malloc,请参阅 §18.6.1/4(operator new
的默认行为(:
默认行为:
- 执行循环:在循环中,函数首先尝试分配请求的存储。未指定尝试是否涉及对标准 C 库函数 malloc 的调用。
避免默认初始化
所以,看到我们想使用new[]
delete[]
,查看有关新表达式§5.3.4/17 中初始化的信息的标准:
创建类型 T 的对象的新表达式按如下方式初始化该对象:
- 如果省略 new-initializer,则对象默认初始化 (8.5(;如果未执行初始化,则对象具有不确定的值。
- 否则,将根据 8.5 的初始化规则解释 new-initializer 以进行直接初始化。
并转到§8.5/7:
默认初始化 T 类型的对象意味着:
- 如果 T 是(可能符合 cv 条件的(类类型(条款 9(,则调用 T 的默认构造函数 (12.1((如果 T 没有默认构造函数或重载解析 (13.3( 导致歧义或函数从初始化上下文中删除或无法访问,则初始化格式不正确(;
- 如果 T 是数组类型,则每个元素都是默认初始化的;
我们看到,如果我们在new[]
中省略一个新初始值设定项,数组的所有元素都将通过其默认构造函数进行默认初始化。
那么,如果我们包含一个新的初始值设定项,我们有什么选择吗?回到 §5.3.2/1 中的定义:
新初始值设定项:
- (表达式列表选项(
- 大括号初始化列表
我们剩下的唯一可能性是大括号的初始化列表(表达式列表适用于非数组新表达式(。我设法让它适用于具有编译时大小的对象,但显然这不是很有帮助。供参考(部分代码改编自此处(:
#include <iostream>
#include <utility>
struct A
{
int id;
A(int i) : id(i) {
std::cout << "c[" << id << "]t";}
A() : A(0) {}
~A() {std::cout << "d[" << id << "]t";}
};
template<class T, std::size_t ...I>
T* template_copy_impl(T* a, std::index_sequence<I...>) {
return new T[sizeof...(I)]{std::move(a[I])...};
}
template<class T, std::size_t N,
typename Indices = std::make_index_sequence<N>>
T* template_copy(T* a) {
return template_copy_impl<T>(a, Indices());
}
int main()
{
const std::size_t N = 3;
A* orig = new A[N];
std::cout << std::endl;
// modify original so we can see whats going on
for (int i = 0; i < N; ++i)
orig[i].id = 1 + i;
A* copy = template_copy<A, N>(orig);
for (int i = 0; i < N; ++i)
copy[i].id *= 10;
delete[] orig;
std::cout << std::endl;
delete[] copy;
std::cout << std::endl;
}
当使用 -std=c++1y
(或等效(编译时,应输出如下内容:
c[0] c[0] c[0]
d[3] d[2] d[1]
d[30] d[20] d[10]
new[]
与delete[]
的不同类型
总而言之,如果我们想使用 delete[]
,我们不仅需要使用 new[]
,而且在省略 new-initializer 时,我们的对象是默认初始化的。那么,如果我们使用基本类型分配内存(在某种程度上类似于使用放置 new(,它将使内存保持未初始化状态,对吗?是的,但是如果内存分配了不同的类型,则未定义删除内存,例如T* ptr = /* whatever */; delete[] ptr
。参见 §5.3.5/2:
在第二种备选方案(删除数组(中,删除的操作数的值可以是空指针值,也可以是由上一个数组新表达式产生的指针值。否则,行为未定义。[注意:这意味着删除表达式的语法必须与new分配的对象类型匹配,而不是与new表达式的语法匹配。
和 §5.3.5/3,它暗示了同样的事情:
在第二种备选方案(删除数组(中,如果要删除的对象的动态类型与其静态类型不同,则行为未定义。
其他选择?
好吧,您仍然可以按照其他人的建议使用unique_ptr
。尽管考虑到您被困在大型代码库中,但这可能不可行。再次作为参考,这是我的谦虚实现可能的样子:
#include <iostream>
#include <memory>
struct A
{
int id;
A(int i) : id(i) {
std::cout << "c[" << id << "]t";}
A() : A(0) {}
~A() {std::cout << "d[" << id << "]t";}
};
template<class T>
struct deleter
{
const bool wrapped;
std::size_t size;
deleter() :
wrapped(true), size(0) {}
explicit deleter(std::size_t uninit_mem_size) :
wrapped(false), size(uninit_mem_size) {}
void operator()(T* ptr)
{
if (wrapped)
delete[] ptr;
else if (ptr)
{
// backwards to emulate destruction order in
// normally allocated arrays
for (std::size_t i = size; i > 0; --i)
ptr[i - 1].~T();
std::return_temporary_buffer<T>(ptr);
}
}
};
// to make it easier on ourselves
template<class T>
using unique_wrap = std::unique_ptr<T[], deleter<T>>;
template<class T>
unique_wrap<T> wrap_buffer(T* orig)
{
return unique_wrap<T>(orig);
}
template<class T>
unique_wrap<T> copy_buffer(T* orig, std::size_t orig_size)
{
// get uninitialized memory
auto mem_pair = std::get_temporary_buffer<T>(orig_size);
// get_temporary_buffer can return less than what we ask for
if (mem_pair.second < orig_size)
{
std::return_temporary_buffer(mem_pair.first);
throw std::bad_alloc();
}
// create a unique ptr with ownership of our memory, making sure to pass
// the size of the uninitialized memory to the deleter
unique_wrap<T> a_copy(mem_pair.first, deleter<T>(orig_size));
// perform the actual copy and return the unique_ptr
std::uninitialized_copy_n(orig, orig_size, a_copy.get());
return a_copy;
}
int main()
{
const std::size_t N = 3;
A* orig = new A[N];
std::cout << std::endl;
// modify original so we can see whats going on
for (int i = 0; i < N; ++i)
orig[i].id = 1 + i;
unique_wrap<A> orig_wrap = wrap_buffer(orig);
{
unique_wrap<A> copy = copy_buffer(orig, N);
for (int i = 0; i < N; ++i)
copy[i].id *= 10;
// if we are passing the original back we can just release it
A* back_to_somewhere = orig_wrap.release();
delete[] back_to_somewhere;
std::cout << std::endl;
}
std::cout << std::endl;
}
哪个应该输出:
c[0] c[0] c[0]
d[3] d[2] d[1]
d[30] d[20] d[10]
最后,您也许可以覆盖全局或类operator new/delete
,但我不建议这样做。
由于 A 已经是指向包装动态分配内存的类的智能指针,因此可以保证在释放所有引用之前不会释放内存。因此,您可以使用简单的数组或向量并复制智能指针,而无需动态分配数组。
例如:
typedef sdt::vector<A<SomeType> > AVector;
AVector copy(AVector in)
{
AVector copyArray = in;
return copyArray;
}
- 二维阵列的动态分配
- 英特尔并行工作室 2015 C++中的 2D 动态分配全局阵列
- 动态分配'string'阵列
- 如何在C 中创建动态分配的2D结构阵列
- 动态分配的结构阵列具有动态分配的结构阵列元素
- 动态分配的输入,并在C 中输出2D阵列
- 从动态分配的阵列获取内存泄漏
- 在动态分配的多维阵列后清理
- 如何在C++中为灵活阵列动态分配内存
- 正确转发到动态分配的阵列的专用化
- 为动态分配的阵列分配更多内存
- 将动态分配的阵列复制到更大的阵列,而不会发生内存泄漏
- 如何高效地克隆动态分配的阵列
- 如何扩展动态分配的阵列
- 常规 2D 阵列与动态分配的 2D 阵列
- 动态分配的阵列,双释放或损坏
- 动态分配 3D 阵列
- 需要一个代码来动态分配 >1GB 内存阵列在 Visual Studio C++
- C++动态分配的2D阵列内存过度分配
- 在c++中动态分配2D阵列的行