Как реализовать простой контейнер с размещением новых и внедрением функциональности?
Мне нужно реализовать контейнер для хранения количества элементов и по какой-то причине он должен работать без какого-либо выделения кучи. Другое требование состоит в том, что элементы контейнера не должны копироваться или перемещаться каким-либо образом. Они должны быть встроены непосредственно в память, выделенную контейнером.
Для этого я решил использовать размещение новых и полностью делегировать управление памятью реализации контейнера (нашел некоторую полезную информацию о размещении новых в drdobbs).
Работающий пример можно найти здесь. (Обратите внимание, что использование new uint8_t[size]
а также std::queue
это просто, чтобы сохранить пример простым. Мой реальный код имеет более сложную реализацию без кучи.)
Это прекрасно работает до сих пор, так как клиентский код должен помещать элементы в контейнер с такими вызовами:
executer.push(new (executer) MyRunnable("Hello", 123));
Теперь я хочу убрать необходимость повторной записи executer
в этом заявлении. Я предпочел бы написать что-то вроде, например:
executer.pushNew(MyRunnable("Hello", 123));
или же
executer.pushNew(MyRunnable, "Hello", 123);
возможно, предоставив соответствующий шаблон, но мне не удалось написать его (пожалуйста, никаких макросов препроцессора).
Я нашел полезную информацию о std::allocator
здесь, в drdobbs, но не знаю, как применить его к моей проблеме (далее статья написана в 2000 году, поэтому не используйте возможные преимущества C++11).
Можно ли помочь мне найти способ, чтобы больше не нужно было давать executer
дважды?
Изменить: После успешного утверждения ответа Jarod42 я обновил мой пример кода здесь.
А для истории, вот оригинальный пример кода моего первоначального вопроса:
#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();
}
2 ответа
С:
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);
}
Первый ответ Jarod42 в том, что вы хотите сделать:
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)...));
}
Это намного легче понять.