如何实现具有 placement new 和 emplace 功能的简单容器?
How to implement a simple container with placement new and emplace functionality?
我需要实现一个容器来容纳一定数量的元素,出于某种原因,它必须在没有任何堆分配的情况下工作。另一个要求是,不应以任何方式复制或移动容器元素。它们必须直接构造到容器分配的内存中。
为此,我决定使用 placement new 并将内存管理完全委托给容器实现(在 drdobbs 找到了一些关于 placement new 的有用信息)。
找到一个 运行 个示例 here。
(请注意,new uint8_t[size]
和 std::queue
的使用只是为了让示例保持简单。我的真实代码有更复杂的无堆实现。)
到目前为止,这非常有效,因为客户端代码必须通过以下调用将元素放入容器中:
executer.push(new (executer) MyRunnable("Hello", 123));
现在我想删除此语句中重复写入 executer
的需要。我宁愿写一些像这样的东西:
executer.pushNew(MyRunnable("Hello", 123));
或
executer.pushNew(MyRunnable, "Hello", 123);
也许可以通过提供一个合适的模板,但我没有写一个(请不要预处理器宏)。
我在 drdobbs 找到了一些关于 std::allocator
的有用信息,但不知道如何将其应用于我的问题(此外,这篇文章是 anno 2000 的,所以不要不要利用可能的 C++11 优势)。
有人能帮我找到一种不再需要给 executer
两次的方法吗?
编辑: 审核通过后's answer, I'd updated my running example code here.
关于历史,这里是我最初问题的原始示例代码:
#include <iostream>
#include <queue>
class Runnable {
// Runnable should be uncopyable and also unmovable
Runnable(const Runnable&) = delete;
Runnable& operator = (const Runnable&) = delete;
Runnable(const Runnable&&) = delete;
Runnable& operator = (const Runnable&&) = delete;
public:
explicit Runnable() {}
virtual ~Runnable() {}
virtual void run() = 0;
};
class MyRunnable: public Runnable {
public:
explicit MyRunnable(const char* name, int num): name(name), num(num) {}
virtual void run() override {
std::cout << name << " " << num << std::endl;
}
private:
const char* name;
int num;
};
class Executer {
// Executer should be uncopyable and also unmovable
Executer(const Executer&) = delete;
Executer& operator = (const Executer&) = delete;
Executer(const Executer&&) = delete;
Executer& operator = (const Executer&&) = delete;
public:
explicit Executer() {
}
void* allocateEntry(size_t size) {
// this heap allocation is just to keep this example simple
// my real implementation uses it's own memory management instead (blockpool)
return new uint8_t[size];
}
void push(Runnable* entry) {
queue.push(entry);
}
template <typename R> // this don't works
void pushNew(R) {
push(new (*this) R);
}
inline friend void* operator new(size_t n, Executer& executer) {
return executer.allocateEntry(n);
}
void execute() {
while (queue.size() > 0) {
Runnable* entry = queue.front();
queue.pop();
entry->run();
// Now doing "placement delete"
entry->~Runnable();
uint8_t* p = reinterpret_cast<uint8_t*>(entry);
delete[] p;
}
}
private:
// this use of std::queue is just to keep this example simple
// my real implementation uses it's own heap-less queue instead
std::queue<Runnable*> queue {};
};
int main() {
Executer executer;
executer.push(new (executer) MyRunnable("First", 1));
executer.push(new (executer) MyRunnable("Second", 2));
executer.push(new (executer) MyRunnable("Third", 3));
// but want to use it more like one this
//executer.pushNew(MyRunnable("Fifth", 5)); // how to implement it?
//executer.pushNew(MyRunnable, "Sixth", 6); // or maybe for this usage?
executer.execute();
}
与:
template <typename R, typename... Ts>
void pushNew(Ts&&... args) {
push(new (*this) R(std::forward<Ts>(args)...));
}
你可以这样写:
executor.PushNew<MyRunnable>("Hello", 123);
而不是
executer.push(new (executer) MyRunnable("Hello", 123));
这有两个问题:
template <typename R> // this don't works
void pushNew(R) {
push(new (*this) R);
}
第一个由 回答,你想做的是:
template <typename R, typename... Ts>
void pushNew(Ts&&... args) {
push(new (*this) R(std::forward<Ts>(args)...));
}
但更重要的是……new (*this) R
真的很奇怪。看起来你正在为自己构建一个 R
!但你不是,你只是在使用该语法来调用你的分配器。这严重违反了最小惊奇原则。我花了很长时间才明白发生了什么。
你应该做的就是直接使用你的分配器:
template <typename R, typename... Ts>
void pushNew(Ts&&... args) {
void* slot = allocateEntry(sizeof(R));
push(new (slot) R(std::forward<Ts>(args)...));
}
这样就容易理解多了。
我需要实现一个容器来容纳一定数量的元素,出于某种原因,它必须在没有任何堆分配的情况下工作。另一个要求是,不应以任何方式复制或移动容器元素。它们必须直接构造到容器分配的内存中。
为此,我决定使用 placement new 并将内存管理完全委托给容器实现(在 drdobbs 找到了一些关于 placement new 的有用信息)。
找到一个 运行 个示例 here。
(请注意,new uint8_t[size]
和 std::queue
的使用只是为了让示例保持简单。我的真实代码有更复杂的无堆实现。)
到目前为止,这非常有效,因为客户端代码必须通过以下调用将元素放入容器中:
executer.push(new (executer) MyRunnable("Hello", 123));
现在我想删除此语句中重复写入 executer
的需要。我宁愿写一些像这样的东西:
executer.pushNew(MyRunnable("Hello", 123));
或
executer.pushNew(MyRunnable, "Hello", 123);
也许可以通过提供一个合适的模板,但我没有写一个(请不要预处理器宏)。
我在 drdobbs 找到了一些关于 std::allocator
的有用信息,但不知道如何将其应用于我的问题(此外,这篇文章是 anno 2000 的,所以不要不要利用可能的 C++11 优势)。
有人能帮我找到一种不再需要给 executer
两次的方法吗?
编辑: 审核通过后
关于历史,这里是我最初问题的原始示例代码:
#include <iostream>
#include <queue>
class Runnable {
// Runnable should be uncopyable and also unmovable
Runnable(const Runnable&) = delete;
Runnable& operator = (const Runnable&) = delete;
Runnable(const Runnable&&) = delete;
Runnable& operator = (const Runnable&&) = delete;
public:
explicit Runnable() {}
virtual ~Runnable() {}
virtual void run() = 0;
};
class MyRunnable: public Runnable {
public:
explicit MyRunnable(const char* name, int num): name(name), num(num) {}
virtual void run() override {
std::cout << name << " " << num << std::endl;
}
private:
const char* name;
int num;
};
class Executer {
// Executer should be uncopyable and also unmovable
Executer(const Executer&) = delete;
Executer& operator = (const Executer&) = delete;
Executer(const Executer&&) = delete;
Executer& operator = (const Executer&&) = delete;
public:
explicit Executer() {
}
void* allocateEntry(size_t size) {
// this heap allocation is just to keep this example simple
// my real implementation uses it's own memory management instead (blockpool)
return new uint8_t[size];
}
void push(Runnable* entry) {
queue.push(entry);
}
template <typename R> // this don't works
void pushNew(R) {
push(new (*this) R);
}
inline friend void* operator new(size_t n, Executer& executer) {
return executer.allocateEntry(n);
}
void execute() {
while (queue.size() > 0) {
Runnable* entry = queue.front();
queue.pop();
entry->run();
// Now doing "placement delete"
entry->~Runnable();
uint8_t* p = reinterpret_cast<uint8_t*>(entry);
delete[] p;
}
}
private:
// this use of std::queue is just to keep this example simple
// my real implementation uses it's own heap-less queue instead
std::queue<Runnable*> queue {};
};
int main() {
Executer executer;
executer.push(new (executer) MyRunnable("First", 1));
executer.push(new (executer) MyRunnable("Second", 2));
executer.push(new (executer) MyRunnable("Third", 3));
// but want to use it more like one this
//executer.pushNew(MyRunnable("Fifth", 5)); // how to implement it?
//executer.pushNew(MyRunnable, "Sixth", 6); // or maybe for this usage?
executer.execute();
}
与:
template <typename R, typename... Ts>
void pushNew(Ts&&... args) {
push(new (*this) R(std::forward<Ts>(args)...));
}
你可以这样写:
executor.PushNew<MyRunnable>("Hello", 123);
而不是
executer.push(new (executer) MyRunnable("Hello", 123));
这有两个问题:
template <typename R> // this don't works
void pushNew(R) {
push(new (*this) R);
}
第一个由
template <typename R, typename... Ts>
void pushNew(Ts&&... args) {
push(new (*this) R(std::forward<Ts>(args)...));
}
但更重要的是……new (*this) R
真的很奇怪。看起来你正在为自己构建一个 R
!但你不是,你只是在使用该语法来调用你的分配器。这严重违反了最小惊奇原则。我花了很长时间才明白发生了什么。
你应该做的就是直接使用你的分配器:
template <typename R, typename... Ts>
void pushNew(Ts&&... args) {
void* slot = allocateEntry(sizeof(R));
push(new (slot) R(std::forward<Ts>(args)...));
}
这样就容易理解多了。