我有一个类,它是一个模板化的智能指针,用于包装动态分配的数组。我知道STL中有一些类可以用于此,特别是在C ++ 11中,但这是一个广泛使用的内部类。
我希望为它编写一个Clone()方法。我的初始实现使用了std :: copy,但我意识到在分配数组时我应该能够避免默认构造。
PoC上的My attempt最终会出现分段错误:
#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 []'删除它(即视为它被分配了一个简单的'new 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()
被调用的方式重写此函数(如果它甚至存在!),同时返回完全相同的结果(让我们假设我们的类型在{{1}中表现良好}和T t; t = other;
是等价的),包括可以使用标准T t(other);
运算符删除函数结果的事实。
答案 0 :(得分:3)
如何创建一个动态分配的T数组,用std :: uninitialized_copy初始化,以便可以用'delete []'删除它(即视为它被分配了一个简单的'new T [N] “)?
因此,考虑到能够使用delete[]
删除内存的相对简单的要求,让我们看看我们有哪些选项。
注意:标准中的所有引用都来自C ++ 14草案N3797,而且我不是标准解释中最好的,所以请稍等一下。
malloc()/free()
和new[]/delete[]
未定义,因为new不一定调用malloc,请参阅§18.6.1/ 4 (operator new
的默认行为):
默认行为:
- 执行循环:在循环内,函数首先尝试分配请求的存储。是否未指定尝试是否涉及调用标准C库函数malloc。
因此,如果我们想使用new[]
,我们需要使用delete[]
,
在 new-expression §5.3.4/ 17 中查看有关初始化的信息标准:
创建类型为T的对象的new-expression初始化该对象,如下所示:
- 如果省略new-initializer,则默认初始化对象(8.5);如果没有执行初始化,则该对象具有不确定的值。
- 否则,新的初始化程序将根据8.5的初始化规则进行解释,以进行直接初始化。
并转到§8.5/ 7 :
默认初始化T类型的对象意味着:
- 如果T是(可能是cv限定的)类类型(第9节),则调用T的默认构造函数(12.1)(如果T没有默认构造函数或重载解析(13.3),则初始化是错误的)导致歧义或在初始化的上下文中删除或无法访问的函数中;;
- 如果T是数组类型,则每个元素都是默认初始化的;
我们看到如果我们在new[]
中省略 new-initializer ,那么数组的所有元素都将通过默认的构造函数进行默认初始化。
那么,如果我们包含 new-initializer ,我们有什么选择吗?回到§5.3.2/ 1 :
中的定义新初始化:
- (表达式列表选项)
- 支撑-INIT列表
我们留下的唯一可能性是 braced-init-list (表达式列表用于非数组 new-expressions )。我设法让它适用于具有编译时间大小的对象,但显然这并不是非常有用。供参考(代码部分改编自here):
#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[]
总结一下,如果我们想要使用new[]
,我们不仅需要使用delete[]
,而且当省略 new-initializer 时,我们的对象也会默认初始化。那么,如果我们使用基本类型分配内存(类似地,在某种程度上,使用placement new),它将使内存保持未初始化,对吧?是的,但是如果它被分配了不同的类型,则未定义用T* ptr = /* whatever */; delete[] ptr
之类的内容删除内存。见§5.3.5/ 2 :
在第二个备选(删除数组)中,delete操作数的值可以是空指针值或由前一个数组new-expression产生的指针值。如果不是,则行为未定义。 [注意:这意味着delete-expression的语法必须与new分配的对象的类型匹配,而不是new-expression的语法。 - 结束说明]
和§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
,但我不建议。
答案 1 :(得分:0)
由于A已经是指向包装动态分配内存的类的智能指针,因此它保证在释放所有引用之前不会释放内存。因此,您可以使用简单的数组或向量并复制智能指针,无需动态分配数组。
例如:
typedef sdt::vector<A<SomeType> > AVector;
AVector copy(AVector in)
{
AVector copyArray = in;
return copyArray;
}