具有不可复制不可移动元素类型的 C++ 容器
C++ container with non-copyable non-movable element type
我需要一个既不可复制也不可移动的元素容器。这些元素不是默认可构造的,但它们的构造函数获得相同的参数。
容器的大小在其生命周期内不会改变。它应该像内置数组一样简单,但它的大小是在调用构造函数时 运行 时确定的。
有没有一种简单的方法可以实现它,而不会因为使用 std::vector<std::unique_ptr<T>>
而产生内存分配和间接寻址的开销?
这是一个简单但不完整的解决方案,假设每个元素都是用相同的参数构造的。它使用 placement new
to construct the elements in-place (see also this SO question):
#include <cstdlib>
#include <utility>
#include <new>
// sample structure, non-copyable, non-moveable, non-default-constructible
struct Foo
{
Foo() = delete;
Foo(const Foo&) = delete;
Foo& operator = (const Foo&) = delete;
Foo(Foo&&) = delete;
Foo& operator = (Foo&&) = delete;
Foo(int a, char b, double c) : m_a(a), m_b(b), m_c(c) { }
int m_a;
char m_b;
double m_c;
};
template <typename T>
struct MyArray
{
// Array ctor constructs all elements in-place using the
// provided parameters
template <typename... Args>
MyArray(std::size_t sz, Args&&... args)
: m_sz(sz),
m_data(static_cast<T*>(malloc(sz * sizeof(T))))
{
for (std::size_t i=0; i<m_sz; ++i)
{
new (&m_data[i]) T(std::forward<Args>(args)...);
}
}
~MyArray()
{
for (std::size_t i=0; i<m_sz; ++i)
{
m_data[i].~T();
}
free(m_data);
}
std::size_t m_sz;
T *m_data;
};
int main()
{
Foo foo(1, '2', 3.0);
std::size_t s = 5;
MyArray<Foo> foo_arr(s, 1, '2', 3.0);
}
请注意,缺少一些内容:
- 如果在
MyArray
的构造函数中抛出异常,此基本实现将泄漏内存。
- 您可能需要一个迭代器实现、
begin()
/end()
运算符等,以便更方便并获得与标准容器提供的相同的行为。
- 为了便于说明,我也没有费心进行适当的封装。您可能应该将
m_sz
和 m_data
设为私有成员。
由于您要管理的元素既不可移动也不可复制,因此容器只能包含指向元素的指针。在不了解更多信息或您的要求的情况下,很难猜测原始指针或 std::unique_ptr
哪个更合适。
对于固定大小的容器,std::array
可能是合适的。不幸的是,大小必须是编译时表达式。
另一种方法是使用原始数组,并使用 placement new 就地构建元素。您可以尝试使用向量而不是原始数组,因为向量像普通数组一样使用连续内存,但我无法想象如何使用它来存储不可复制、不可移动和非默认可构造对象。
顺便说一句,在 C++ 的编译时,在大小未知的非默认可构造对象上构建数组并不是那么简单。我能找到的唯一方法是构建一个适当大小的 char 数组,声明一个指向指向数组开头的 class 的指针,然后使用 new:
位置构建元素
char buf = new char[n * sizeof(X)];
X* x = reinterpret_cast<X*>(buf);
for(int i=-1; i<n i++) {
new (x + i) X(i); // or whatever appropriate ctor...
}
"Is there an easy way to implement that without the overhead of memory allocation and indirection incurred by using std::vector>?"
开销如此之小,你为什么要关心?这几乎可以肯定是过早的优化,你只会让自己成为维护的头疼。
我正在尝试做同样的事情,并且我使用了一个简单的解决方法。
这个例子有什么问题?
class test{
const int a;
public:
test(int i): a(i) {} //no default constructor
};
//trick for static initializer
template <typename T> class defer_params: public T{
public:
defer_params(): T(1) {} //fixed params
};
//static array
//test list1[5]; //this doesn't work
defer_params<test> list2[5];
此致,
加布里埃尔
我需要一个既不可复制也不可移动的元素容器。这些元素不是默认可构造的,但它们的构造函数获得相同的参数。
容器的大小在其生命周期内不会改变。它应该像内置数组一样简单,但它的大小是在调用构造函数时 运行 时确定的。
有没有一种简单的方法可以实现它,而不会因为使用 std::vector<std::unique_ptr<T>>
而产生内存分配和间接寻址的开销?
这是一个简单但不完整的解决方案,假设每个元素都是用相同的参数构造的。它使用 placement new
to construct the elements in-place (see also this SO question):
#include <cstdlib>
#include <utility>
#include <new>
// sample structure, non-copyable, non-moveable, non-default-constructible
struct Foo
{
Foo() = delete;
Foo(const Foo&) = delete;
Foo& operator = (const Foo&) = delete;
Foo(Foo&&) = delete;
Foo& operator = (Foo&&) = delete;
Foo(int a, char b, double c) : m_a(a), m_b(b), m_c(c) { }
int m_a;
char m_b;
double m_c;
};
template <typename T>
struct MyArray
{
// Array ctor constructs all elements in-place using the
// provided parameters
template <typename... Args>
MyArray(std::size_t sz, Args&&... args)
: m_sz(sz),
m_data(static_cast<T*>(malloc(sz * sizeof(T))))
{
for (std::size_t i=0; i<m_sz; ++i)
{
new (&m_data[i]) T(std::forward<Args>(args)...);
}
}
~MyArray()
{
for (std::size_t i=0; i<m_sz; ++i)
{
m_data[i].~T();
}
free(m_data);
}
std::size_t m_sz;
T *m_data;
};
int main()
{
Foo foo(1, '2', 3.0);
std::size_t s = 5;
MyArray<Foo> foo_arr(s, 1, '2', 3.0);
}
请注意,缺少一些内容:
- 如果在
MyArray
的构造函数中抛出异常,此基本实现将泄漏内存。 - 您可能需要一个迭代器实现、
begin()
/end()
运算符等,以便更方便并获得与标准容器提供的相同的行为。 - 为了便于说明,我也没有费心进行适当的封装。您可能应该将
m_sz
和m_data
设为私有成员。
由于您要管理的元素既不可移动也不可复制,因此容器只能包含指向元素的指针。在不了解更多信息或您的要求的情况下,很难猜测原始指针或 std::unique_ptr
哪个更合适。
对于固定大小的容器,std::array
可能是合适的。不幸的是,大小必须是编译时表达式。
另一种方法是使用原始数组,并使用 placement new 就地构建元素。您可以尝试使用向量而不是原始数组,因为向量像普通数组一样使用连续内存,但我无法想象如何使用它来存储不可复制、不可移动和非默认可构造对象。
顺便说一句,在 C++ 的编译时,在大小未知的非默认可构造对象上构建数组并不是那么简单。我能找到的唯一方法是构建一个适当大小的 char 数组,声明一个指向指向数组开头的 class 的指针,然后使用 new:
位置构建元素char buf = new char[n * sizeof(X)];
X* x = reinterpret_cast<X*>(buf);
for(int i=-1; i<n i++) {
new (x + i) X(i); // or whatever appropriate ctor...
}
"Is there an easy way to implement that without the overhead of memory allocation and indirection incurred by using std::vector>?"
开销如此之小,你为什么要关心?这几乎可以肯定是过早的优化,你只会让自己成为维护的头疼。
我正在尝试做同样的事情,并且我使用了一个简单的解决方法。 这个例子有什么问题?
class test{
const int a;
public:
test(int i): a(i) {} //no default constructor
};
//trick for static initializer
template <typename T> class defer_params: public T{
public:
defer_params(): T(1) {} //fixed params
};
//static array
//test list1[5]; //this doesn't work
defer_params<test> list2[5];
此致, 加布里埃尔