将使用 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();
main.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 &)': 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&&) = default;
重新启用移动构造函数。然后,您可能会遇到在 main.cpp
中使用不完整类型的问题:)
我把emplace换成了c++;最多有 5 个标签,并且有 C++ 很有用。
【参考方案1】:
在您的 cpp 文件中添加 Test(Test&&) noexcept;
和 Test& operator=(Test&&) 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 中的主要内容,如果未能解决你的问题,请参考以下文章