How to implement a simple container with placement new and emplace functionality?

元气小坏坏 提交于 2019-12-19 08:08:09

问题


I need to implement a container to hold an amount of elements and for some reason, it has to work without any heap allocation. Another requirement is, that the container elements should not be copied or moved in any way. They have to constructed directly into the memory allocated by the container.

For that, I decided to use placement new and delegate the memory management completely to the container implementation (found some useful information about placement new at drdobbs).

A running example is found here. (Please note, that the use of new uint8_t[size] and std::queue is just to keep the example simple. My real code has more complex, heap-less implementation instead.)

This perfectly works so far, as the client code has to put elements into the container with calls like:

executer.push(new (executer) MyRunnable("Hello", 123));

Now I want do remove the need of the repeated write executer in this statement. I would rather like to write something like e.g.:

executer.pushNew(MyRunnable("Hello", 123));

or

executer.pushNew(MyRunnable, "Hello", 123);

maybe by providing an appropriate template but I failed to write one (no preprocessor macros, please).

I'd found some useful information about std::allocator here at drdobbs but don't know how to apply it to my problem (further, the article is of anno 2000 and so don't take use of possible C++11 advantages).

Could one help me to find a way to not longer need to give the executer twice?

Edit: After successful approving Jarod42's answer, I'd updated my running example code here.

And for the history, here the original example code of my initial question:

#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();
}

回答1:


With:

template <typename R, typename... Ts>
void pushNew(Ts&&... args) {
    push(new (*this) R(std::forward<Ts>(args)...));
}

You can write:

executor.PushNew<MyRunnable>("Hello", 123);

instead of

executer.push(new (executer) MyRunnable("Hello", 123));



回答2:


There are two things wrong with this:

template <typename R> // this don't works
void pushNew(R) {
    push(new (*this) R);
}

The first is answered by Jarod42 in that you want to do:

template <typename R, typename... Ts>
void pushNew(Ts&&... args) {
    push(new (*this) R(std::forward<Ts>(args)...));
}

but even more importantly... new (*this) R is really bizarre. It looks like you're constructing an R over yourself! But you're not, you're just using that syntax to call your allocator. That horribly violates the principle of least surprise. It took me quite a while to understand what was going on.

What you should to is just use your allocator directly:

template <typename R, typename... Ts>
void pushNew(Ts&&... args) {
    void* slot = allocateEntry(sizeof(R));
    push(new (slot) R(std::forward<Ts>(args)...));
}

That is a lot easier to understand.



来源:https://stackoverflow.com/questions/32926042/how-to-implement-a-simple-container-with-placement-new-and-emplace-functionality

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!