【问题标题】:How to implement a simple container with placement new and emplace functionality?如何实现一个具有放置新和就位功能的简单容器?
【发布时间】:2015-10-03 18:33:15
【问题描述】:

我需要实现一个容器来容纳大量元素,并且由于某种原因,它必须在没有任何堆分配的情况下工作。另一个要求是,不应以任何方式复制或移动容器元素。它们必须直接构造到容器分配的内存中。

为此,我决定使用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 两次的方法吗?

编辑:在成功批准Jarod42的答案后,我更新了我的运行示例代码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();
}

【问题讨论】:

  • 在编译时是否知道元素的数量?你能用std::array吗?
  • 不,std::array 不能使用。此外,容器管理内存的方式必须是完全私有的。
  • 你看过 emplace back 的 std::vectors 实现了吗?听起来这正是您所需要的(忽略 heep vs stack 问题,这是一个单独的问题)
  • 想想看,你甚至可以用堆栈分配器围绕 std::vector 构建一个包装器
  • 您可以传递参数(namenum),而不是将 MyRunnable 传递给 push()?然后你可以在push() 中添加new

标签: c++ c++11 containers placement-new emplace


【解决方案1】:

这有两个问题:

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)...));
}

这更容易理解。

【讨论】:

  • 应该被接受。随机导航员注意这是我更喜欢的答案! :)
【解决方案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));

【讨论】:

  • 虽然这是公认的答案,但巴里的答案要好得多。
猜你喜欢
  • 2015-12-31
  • 2010-09-09
  • 2012-07-25
  • 1970-01-01
  • 2016-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-12
相关资源
最近更新 更多