【问题标题】:How to insert a move-only object into a map in Visual C++?如何在 Visual C++ 中将只移动对象插入到地图中?
【发布时间】:2017-06-26 02:08:21
【问题描述】:

我有一个类 (A),其复制构造函数已删除 (=delete;)、移动构造函数和移动赋值声明和定义。该类拥有一个不能被释放两次的资源,因此有只能移动的限制。

我有另一个类 (B),它包含 A 的实例(不是指针)。我为 B 类编写了一个移动赋值运算符,它在其 A 类的成员上调用 std::move()

一张地图包含许多 B 的实例。我创建了一个 B 的实例,并通过赋值直接分配它的 A 成员。然后我尝试将 B 插入到地图中。我以不同的方式尝试了operator[]insertemplace,但我不断收到错误消息,声称我正在尝试使用签名为A(const A&) 的已删除函数。所以它试图使用复制构造函数,尽管它被删除了?

在我尝试将其插入地图之前,一切都很好。

我正在使用 VS2013。

编辑:对于我的项目(可能与所讨论的问题无关),我最终使用的方法执行与 WinAPI(kernel32/ntdll 等)的任何库交互,这一点很重要,所以任何调用operator new() 或类似的内部委托给WinAPI的任何方法我都将无法使用。该地图已预先分配了reserve() 以尝试缓解这种情况。

代码(对emplace的调用是导致编译错误的原因):

/* main.cpp */

#include "smart_handle.h"
#include <unordered_map>
#include <Windows.h>

struct MetadataStruct
{
    MetadataStruct& operator=(MetadataStruct&& other)
    {
        if (this != &other)
        {
            handle = std::move(other.handle);
            // Invalidate other.handle somehow...
        }
        return *this;
    }

    Util::SmartHandle handle;
    // Also has other members.
};

std::unordered_map<DWORD, MetadataStruct> metadataStructs;

Util::SmartHandle GetHandle()
{
    // Doing lots of stuff here to get the handle, then finally wraps
    // it before returning, hence the need for its own function.
    const HANDLE openedFileHandle = (HANDLE)-1; // Just an example handle.
    return std::move(Util::SmartHandle(openedFileHandle, NULL));
}

void F()
{
    MetadataStruct metadataStruct;
    metadataStruct.handle = GetHandle();

    metadataStructs.emplace(0, std::move(metadataStruct));
}

int main(int argc, char** argv)
{
    F();
    return 0;
}

/* smart_handle.h */
#pragma once

#include <stdexcept>
#include <Windows.h>

namespace Util
{
    class SmartHandle
    {
    public:
        SmartHandle() = default;
        SmartHandle(HANDLE handle, HANDLE invalidHandleValue);

        SmartHandle(const SmartHandle& other) = delete;
        SmartHandle& operator=(const SmartHandle& other) = delete;

        SmartHandle(SmartHandle&& other);
        SmartHandle& operator=(SmartHandle&& other);

        ~SmartHandle();

        HANDLE GetValue() const;
        bool IsValid() const;
        void Close();
    private:
        static const HANDLE uninitializedHandleValue;

        HANDLE handle = uninitializedHandleValue;
        HANDLE invalidHandleValue = uninitializedHandleValue;

        void Set(HANDLE handle, HANDLE invalidHandleValue, bool throwIfInvalid = true);
    };
}

/* smart_handle.cpp */

#include "smart_handle.h"

namespace Util
{
    const HANDLE SmartHandle::uninitializedHandleValue = (HANDLE)-2;

    SmartHandle::SmartHandle(const HANDLE handle, const HANDLE invalidHandleValue)
    {
        Set(handle, invalidHandleValue);
    }

    SmartHandle::SmartHandle(SmartHandle&& other)
    {
        handle = other.handle;
        invalidHandleValue = other.invalidHandleValue;

        other.handle = uninitializedHandleValue;
        other.invalidHandleValue = uninitializedHandleValue;
    }

    SmartHandle& SmartHandle::operator=(SmartHandle&& other)
    {
        if (this != &other)
        {
            handle = other.handle;
            invalidHandleValue = other.invalidHandleValue;

            other.handle = uninitializedHandleValue;
            other.invalidHandleValue = uninitializedHandleValue;
        }
        return *this;
    }

    SmartHandle::~SmartHandle()
    {
        Close();
    }

    void SmartHandle::Set(const HANDLE handle, const HANDLE invalidHandleValue, const bool throwIfInvalid)
    {
        this->handle = handle;
        this->invalidHandleValue = invalidHandleValue;

        if (throwIfInvalid && !IsValid())
        {
            this->handle = uninitializedHandleValue;
            this->invalidHandleValue = uninitializedHandleValue;
            throw std::invalid_argument("The handle used to initialize the object is not a valid handle");
        }
    }

    HANDLE SmartHandle::GetValue() const
    {
        if (handle == uninitializedHandleValue)
        {
            throw std::exception("Handle value not initialized");
        }

        return handle;
    }

    bool SmartHandle::IsValid() const
    {
        return handle != uninitializedHandleValue && handle != invalidHandleValue;
    }

    void SmartHandle::Close()
    {
        const bool isPseudoHandle = handle == (HANDLE)-1;

        if (IsValid() && !isPseudoHandle)
        {
            if (!CloseHandle(handle))
            {
                throw std::exception("CloseHandle failed");
            }

            handle = invalidHandleValue;
        }
    }
}

【问题讨论】:

  • 附注:管理资源有更简单的方法。它们可能需要更多代码(不多),但您不必忍受不可复制的对象。最简单的方法(也是需要更少代码的方法):将您的资源包装到std::shared_ptr。它会为您解决一切问题。

标签: c++ visual-c++ move copy-constructor move-semantics


【解决方案1】:

您可以使用emplace,但您需要结合使用std::move,以便将您已有的对象转换为右值引用。 std::unique_ptr 只能移动,你可以把它放到地图中,就像

int main() {
    std::unique_ptr<int> foo;
    std::map<int, std::unique_ptr<int>> bar;
    bar.emplace(1, std::move(foo));
}

【讨论】:

  • 但是unique_ptr 只适用于指针,对吧?当我尝试将对象 A 封装在一个智能指针中时,它告诉我它不支持它,但是如果我在对象创建前加上 new 那么它就可以工作。我需要在堆栈上创建对象,所以我不能使用new
  • @Mikubyte std::unique_ptr 在堆栈上。在代码中我从未使用过new。这只是一个示例,因为您没有显示任何代码。如果您用实际拥有的内容更新您的问题,我可以为您提供更量身定制的代码块。
  • 添加了一些示例代码,这些代码抓住了我正在尝试做的事情的要点。
  • @Mikubyte 好的。所以你需要的是m.emplace(..., std::move(b)); 而不是m.emplace(..., b); 这会将b 保存的内容移动到地图中,并使b 处于有效/未指定但可破坏的状态。
  • @Mikubyte 您的代码有很多错误,我无法编译。在尽我所能之后,我得到了this,它确实有效。如果没有minimal reproducible example,我真的无能为力
猜你喜欢
  • 1970-01-01
  • 2012-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-16
  • 1970-01-01
  • 2019-04-26
相关资源
最近更新 更多