emplace_back 初始化列表错误,当初始化列表作用于独立变量时

emplace_back initialisation list error, when initialisation list works on standalone variable

所以我一直在做一个对象池class,它是这样使用的:

class MagicTrick {
public:
    MagicTrick(int magic) : _magic(magic)
    {}
    int predict() {
        return _number * _magic;
    }
private:
    int _magic;
    int _number = 2;
};
const std::size_t poolSize = 1;
ObjectPool<MagicTrick> magicTrickPool(poolSize, 5);

const int number = magicTrickPool.schedule([](MagicTrick& magicTrick){
    return magicTrick.predict();
});

这很好用,但是当线程池使用的对象删除了它的复制构造函数时,例如数据成员是 std::unique_ptr 池的构造失败。 在内部我使用一个向量来存储池:

struct ObjectAndLock {
    Object object;
    bool free;
    static bool isFree(const ObjectAndLock& objectAndLock) {
        return objectAndLock.free;
    }
};
std::vector<ObjectAndLock> objectAndLocks;

我构建了完整的池class:

template<typename Object> 
class ObjectPool {
    template<typename ...Args>
    ObjectPool(std::size_t poolSize, Args&&... objectArgs) 
        : objectAndLocks(poolSize, { {std::forward<Args>(objectArgs)...}, true}) 
{}

这将使用此处列出的第三个重载构造向量 https://en.cppreference.com/w/cpp/container/vector/vector

然而,这会将元素复制到向量中。 所以我将其更改为 emplace_back 以构建向量中的对象,如下所示:

template<typename Object> 
class ObjectPool {
    template<typename ...Args>
    ObjectPool(std::size_t poolSize, Args&&... objectArgs)
    {
        if(poolSize == 0){
            throw std::runtime_error("poolSize must be greater than 0");
        }
        objectAndLocks.reserve(poolSize);
        for (std::size_t i = 0; i < poolSize; i++)
        {
            objectAndLocks.emplace_back({Object{std::forward<Args>(objectArgs)...}, true});
        }
    }
} 

然而错误在于:

Projects\ObjectPool\public_include\ObjectPool\ObjectPool.hpp(87): error C2660: 'std::vector<object_pool::ObjectPool<MagicTrick>::ObjectAndLock,std::allocator<_Ty>>::emplace_back': function does not take 1 arguments
          with
          [
              _Ty=object_pool::ObjectPool<MagicTrick>::ObjectAndLock
          ]
  C:\Program Files (x86)\Microsoft Visual Studio19\Community\VC\Tools\MSVC.23.28105\include\vector(651): note: see declaration of 'std::vector<object_pool::ObjectPool<MagicTrick>::ObjectAndLock,std::allocator<_Ty>>::emplace_back'
          with
          [
              _Ty=object_pool::ObjectPool<MagicTrick>::ObjectAndLock
          ]

但是我可以像这样使用初始化列表在构造函数中创建一个对象,它编译得很好。

ObjectAndLock hello = { Object{std::forward<Args>(objectArgs)...}, true };

我看到了这个答案,但是我无法让它工作: 我将 std::initializer_list 的模板用作:

std::initializer_list<ObjectAndLock>

也许这是错误的?

所以我的问题是如何让 emplace_back 正常工作?我最多可以使用 c++17

这是一个失败的例子 class,因为它是不可复制的:

struct NonCopyable {
    std::unique_ptr<int> number = std::make_unique<int>(10);
    NonCopyable(const NonCopyable& other) = delete;
    NonCopyable& operator=(const NonCopyable& other) = delete;
};

为完整起见,这里是完整的 class:

#ifndef OBJECTPOOL_H
#define OBJECTPOOL_H
#include <vector>
#include <functional>
#include <map>
#include <mutex>
#include <condition_variable>
#include <type_traits>
#include <algorithm>
#include <stdexcept>
#include <exception>
namespace object_pool {
    namespace internal {
        template <typename Function>
        class DeferToDestruction {
            Function _function;
        public:
            DeferToDestruction(Function function) : _function(function) {}
            ~DeferToDestruction() { _function(); }
        };
    }
    template<typename Object> 
    class ObjectPool {
    public:
        /*!
        @brief Create an object pool for 

        @param poolSize - Size of object pool, this must be atleast 1
        @param objectArgs... - Arguments to construct the objects in the pool

        Complete Example:
        @code
            class MagicTrick {
            public:
                MagicTrick(int magic) : _magic(magic)
                {}
                int predict() {
                    return _number * _magic;
                }
            private:
                int _magic;
                int _number = 2;
            };

            std::size_t poolSize = 5;
            object_pool::ObjectPool<MagicTrick> magicTrickPool(poolSize, 5);

            const int number = magicTrickPool.schedule([](MagicTrick& magicTrick){
                return magicTrick.predict();
            });
        @endcode

        Zero Argument Constructor Example:
        @code            
            struct ZeroArgs {
                int number = 2;
            };

            object_pool::ObjectPool<ZeroArgs> zeroArgsPool(1);
        @endcode

        Multiple Argument Constructor Example:
        @code
            class MultiArgs {
            public:
                MultiArgs(std::string name, int age, bool alive) {
                   _number = name.size() + age + (alive ? 5 : -5);
                }
                int predict() {
                    return _number * 2;
                }
            private:
                int _number = 2;
            };

            object_pool::ObjectPool<MultiArgs> multiArgsPool(1, "bob", 99, true);
        @endcode
        */
        template<typename ...Args>
        ObjectPool(std::size_t poolSize, Args&&... objectArgs)
        {
            if(poolSize == 0){
                throw std::runtime_error("poolSize must be greater than 0");
            }
            objectAndLocks.reserve(poolSize);
            for (std::size_t i = 0; i < poolSize; i++)
            {
                objectAndLocks.emplace_back({Object{std::forward<Args>(objectArgs)...}, true});
            }
        }

        ~ObjectPool(){ 

            std::unique_lock<std::mutex> lock(objectAndLocksMutex);            
            const auto allobjectAndLocksFree = [this]() {
                return std::all_of(std::begin(objectAndLocks), std::end(objectAndLocks), ObjectAndLock::isFree);
            };
            if(allobjectAndLocksFree()) {
                return;
            }
            conditionVariable.wait(lock, allobjectAndLocksFree);
        }

        /*!
        @brief Schedule access to the pool

        @param callback - An callable with the the argument being a reference to the class stored in the object pool.

        @return Returns return from the callback function, including void

        Simple Example:
        @code
            const int number = magicTrickPool.schedule([](MagicTrick& magicTrick){
                return magicTrick.predict();
            });
        @endcode
        */
        template<typename FunctionWithObjectAsParameter>
        auto schedule(FunctionWithObjectAsParameter&& callback)
        {
            const auto findFreeObject = [this]() {
                return std::find_if(std::begin(objectAndLocks), std::end(objectAndLocks), ObjectAndLock::isFree);
            };

            std::unique_lock<std::mutex> lock(objectAndLocksMutex);
            auto freeObject = findFreeObject();
            if(freeObject == std::end(objectAndLocks)) {
                conditionVariable.wait(lock, [this, &freeObject, &findFreeObject]{
                    freeObject = findFreeObject();
                    return freeObject != std::end(objectAndLocks);
                });
            }
            freeObject->free = false;
            lock.unlock();
            internal::DeferToDestruction freeAndUnlockAndNotify([this, &freeObject] () {
                {
                    std::scoped_lock<std::mutex> lock(objectAndLocksMutex);
                    freeObject->free = true;
                }
                conditionVariable.notify_one();
            });
            return callback(freeObject->object);
        }
    private:    
        struct ObjectAndLock {
            Object object;
            bool free;
            static bool isFree(const ObjectAndLock& objectAndLock) {
                return objectAndLock.free;
            }        
        };
        std::vector<ObjectAndLock> objectAndLocks;
        std::mutex objectAndLocksMutex;
        std::condition_variable conditionVariable;
    };
}
#endif

如果你看emplace_back

的签名
template <class... Args>
reference emplace_back(Args&&... args);

你会发现emplace_back的参数类型是从你传递的参数推导出来的。 braced-init-list 可用于为特定类型的参数初始化参数。但是 {…} 本身没有类型,因此不能用于从中推断参数的类型。

emplace_back 所做的只是 std::forward 您将传递给它的任何参数传递给元素类型的构造函数以在向量中就地创建元素。问题是你的

struct ObjectAndLock {
    Object object;
    bool free;
    static bool isFree(const ObjectAndLock& objectAndLock) {
        return objectAndLock.free;
    }
};

甚至没有接受参数的构造函数(隐式复制和移动构造函数除外)。

你需要做的是

objectAndLocks.emplace_back(ObjectAndLock{Object{std::forward<Args>(objectArgs)...}, true});

即,为 emplace_back 初始化正确类型的值以转发给隐式移动构造函数。但这本质上和做

是一样的
objectAndLocks.push_back({Object{std::forward<Args>(objectArgs)...}, true});

braced-init-list 适用于 push_back 因为 push_back

void push_back(const T& value);
void push_back(T&& value);

确实期望元素类型的值而不是一组转发引用,因此,{…} 将最终初始化适当类型的参数…

C++20 将通过 T(…) 为聚合引入 直接初始化 ,这将使简单地编写

成为可能
objectAndLocks.emplace_back(Object{std::forward<Args>(objectArgs)...}, true);

在这里。在那之前,我建议在这种情况下只使用 push_back

你的

struct NonCopyable {
    std::unique_ptr<int> number = std::make_unique<int>(10);
    NonCopyable(const NonCopyable& other) = delete;
    NonCopyable& operator=(const NonCopyable& other) = delete;
};

将不可复制也不可移动。您声明了一个复制构造函数,这意味着不会有隐式声明的移动构造函数 [class.copy]/8.1. The story is pretty much the same for the implicitly-declared move assignment operator [class.copy.assign]/4。您不能将不可移动的类型作为 std::vector 的元素类型。要使 NonCopyable 可移动,您必须定义移动构造函数和移动赋值运算符:

struct NonCopyable {
    std::unique_ptr<int> number = std::make_unique<int>(10);

    NonCopyable(const NonCopyable&) = delete;
    NonCopyable(NonCopyable&&) = default;
    NonCopyable& operator=(const NonCopyable&) = delete;
    NonCopyable& operator=(NonCopyable&&) = default;
};