0

I want to do a "delay creator", it will save original object and assign it to a new object which from memory pool in any time(under C++17):

using DelaySpawnFunc = std::function<void(void *)>; // the lambda will capture original object and                             
                                                    // do assignment

class Test final {
public:
    template <typename T>
    void SaveSpawnInfo(T&& obj) {
        DelaySpawnFunc func = [=, c = std::move(obj)](void* elem) { // capture original object
            (*((T *)elem)) = std::move(c);
        };
        spawnFuncs_.push_back(func);
    }

    // call this function in any time I want
    void DoSpawn() {
        for (auto& assign: spawnFuncs_) {
            void* elem = MemoryPool::Instance().Alloc();  // allocate a piece of memory from pool
            assign(elem);
        }
    }

private:
    std::vector<DelaySpawnFunc> spawnFuncs_;
};

It works correctly when I use:

struct NoUniquePtr final {
    int a;
};

int main() {
    Test test;
    test.SaveSpawnInfo<NoUniquePtr>(NoUniquePtr{});
    return 0;
}

But with std::unique_ptr, it can't compile:

struct WithUniquePtr final {
    std::unique_ptr<int> a;
};

int main() {
    Test test;
    test.SaveSpawnInfo<NoUniquePtr>(NoUniquePtr{}); // ERROR: can't compile
    return 0;
}

The error say:

mistaken.cpp(11,28): error C2280: “WithUniquePtr &WithUniquePtr::operator =(const WithUniquePtr &)”: attempts to reference a deleted function
mistaken.cpp(34,1): message : The compiler has been generated here “WithUniquePtr::operator =”
mistaken.cpp(34,1): message : “WithUniquePtr &WithUniquePtr::operator =(const WithUniquePtr &)”: Calls a deleted or inaccessible function due to a data member “std::unique_ptr<int,std::default_delete<int>> &std::unique_ptr<int,std::default_delete<int>>::operator =(const std::unique_ptr<int,std::default_delete<int>> &)”,
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\include\memory(3257,17): message : “std::unique_ptr<int,std::default_delete<int>> &std::unique_ptr<int,std::default_delete<int>>::operator =(const std::unique_ptr<int,std::default_delete<int>> &)”: the function has been implicitly removed

Why it call WithUniquePtr::operator=(const WithUniquePtr&) rather than WithUniquePtr::operator=(WithUniquePtr&&) ? And how can I modify it to compile successful?

VisualGMQ
  • 49
  • 5
  • Aside from the compilation error there are multiple other issues: `std::move(obj)` is incorrect and should use `std::forward(obj)` instead, because `obj` is a forwarding reference, which may be passed an lvalue that shouldn't be moved from. – user17732522 Jun 04 '23 at 17:34
  • The whole concept of `assign` is only valid for implicit-lifetime types. For other types, which includes `WithUniquePtr`, you need to first create an object of the appropriate type with a placement-new in the allocated storage. Otherwise you have undefined behavior as well. – user17732522 Jun 04 '23 at 17:35
  • There also doesn't seem to be any indication that `Alloc` returns memory of suitable size and alignment. Again, if not correctly guaranteed, the code has UB. – user17732522 Jun 04 '23 at 17:36
  • If you `std::move(c)` inside the lambda, then you can't call the lambda twice and you need to make sure of that somehow, e.g. by removing `assign` from the vector once you called it. Also, `elem` is leaked immediately. – user17732522 Jun 04 '23 at 17:38
  • @user17732522 Thank you very much! As you said, `T&&` is a forwarding reference, I should use `T&` for rvalue and maybe I should add `std::is_rvalue_v` to judge it more strict. The other things you adviced also helpful, thanks again. – VisualGMQ Jun 05 '23 at 02:22
  • `T&` would always be an lvalue reference. If you want deduction and only rvalue arguments to move from, then use `T&&`, but add `static_assert(!std::is_reference_v);` as easiest (but not SFINAE-friendly) solution. – user17732522 Jun 05 '23 at 06:01
  • @user17732522 I followed your advice and it works OK, thank you – VisualGMQ Jun 06 '23 at 07:02

0 Answers0