将使用 PIMPL 习语的类存储在 std::vector 中

Posted

技术标签:

【中文标题】将使用 PIMPL 习语的类存储在 std::vector 中【英文标题】:Storing a class that uses the PIMPL idiom in a std::vector 【发布时间】:2021-09-12 15:10:33 【问题描述】:

我正在编写一个应用程序,它需要在std::vector 中存储使用 PIMPL 习惯用法的类的对象。因为该类使用std::unique_ptr 来存储指向其实现的指针并且std::unique_ptr 不可复制,所以该类本身不可复制。 std::vector 在这种情况下应该仍然有效,因为该类仍然是可移动的

为了避免创建副本,我尝试使用emplace_back 将元素直接构造到vector,但由于某种原因它仍然抱怨它正在尝试调用复制构造函数!

我写了一个简单的例子来演示这个问题。

test.h:

#pragma once

#include <memory>

// Simple test class implemented using the PIMPL (pointer to implementation) idiom
class Test

public:
    Test(const int value);
    ~Test();

    void DoSomething();
private:

    // Forward declare implementation struct
    struct Impl;

    // Pointer to the implementation
    std::unique_ptr<Impl> m_impl;
;

test.cpp

#include "test.h"

#include <iostream>

// Implementation struct definition
struct Test::Impl

    Impl(const int value)
        : m_value(value)
    

    void DoSomething()
    
        std::cout << "value = " << m_value << std::endl;
    

private:
    int m_value;
;

// Construct the class and create an instance of the implementation struct
Test::Test(const int value)
    : m_impl(std::make_unique<Impl>(value))


Test::~Test() = default;

// Forward function calls to the implementation struct
void Test::DoSomething()

    m_impl->DoSomething();

ma​​in.cpp:

#include "test.h"

#include <vector>

int main()

    std::vector<Test> v;

    // Even though I'm using "emplace_back" it still seems to be invoking the copy constructor!
    v.emplace_back(42);

    return 0;

当我尝试编译此代码时,我收到以下错误:

error C2280: 'Test::Test(const Test &amp;)': attempting to reference a deleted function

这就引出了两个问题……

    为什么即使我明确使用了emplace_back,它仍试图使用复制构造函数?

    我怎样才能让它编译没有错误?该对象是可移动的,因此根据标准它应该能够存储在std::vector 中。

【问题讨论】:

the class is still movable - 是吗? Test b(std::move(a)); 有效吗? 您应该能够在emplace_back 中调用移动构造函数。似乎此链接可能是您正在寻找的内容:***.com/questions/35404932/… 考虑使用std::shared_ptr 使您的课程可复制。 您声明了析构函数,因此编译器甚至不会尝试提供移动构造函数。它确实提供了一个复制构造函数,但已被弃用。您可以Test(Test&amp;&amp;) = default; 重新启用移动构造函数。然后,您可能会遇到在 main.cpp 中使用不完整类型的问题:) 我把emplace换成了c++;最多有 5 个标签,并且有 C++ 很有用。 【参考方案1】:

在您的 cpp 文件中添加 Test(Test&amp;&amp;) noexcept;Test&amp; operator=(Test&amp;&amp;) noexcept; 然后 =default

您可能应该在使用 Test(const int value) 时明确表示。

至少在modern gcc/clang 中,您可以=default 在标题中移动ctor。您不能对operator= 执行此操作,因为它可以删除左侧指针,也不能删除零参数构造函数(与构造默认删除器有关?)或析构函数(必须调用默认删除器)。

【讨论】:

你知道为什么内联默认移动ctor不能处理不完整的Impl吗? @dyp 不。我知道一些 C++ 标准容器对完整性的要求过于严格。我不认为一个唯一的 ptr move ctor 实际上需要对象的定义的任何根本原因。 @dyp 所以,我试过了,至少在现代 gcc/clang move ctor works 中带有 =default 和未定义的对象。默认 ctor 和 dtor 不起作用。 这里,在 clang+libc++ 中:godbolt.org/z/Mejv4q5nv 看起来我们从 move ctor 实例化了 dtor。 @dyp 当它检查是否可以从源唯一 ptr 复制删除器时似乎会发生这种情况。 godbolt.org/z/583GzzreK 可以解决它。不知道标准是怎么说的。

以上是关于将使用 PIMPL 习语的类存储在 std::vector 中的主要内容,如果未能解决你的问题,请参考以下文章

markdown PIMPL习语

闭包作为数据合并习语的解决方案

通过 std::unique_ptr 的 LazyArray 模板,这是双重检查习语的正确实现吗?

C++ const 正确性漏洞或意外使用?

C++ 程序员应该使用哪些 C++ 习语? [关闭]

C++ 设计篇之——pimpl 机制