提问人:isanae 提问时间:8/19/2023 最后编辑:isanae 更新时间:9/8/2023 访问量:170
std::vector、不完整的类型和继承的构造函数
std::vector, incomplete type and inherited constructors
问:
我有一个带有成员变量的类,该变量已声明但未定义。如果未定义类的析构函数,则可能会出现问题,因为编译器可能会选择其他翻译单元来生成析构函数。如果该 TU 没有 的定义,则编译失败。vector
T
T
struct undefined;
struct S
{
S(int);
std::vector<undefined> v;
};
int main()
{
S s(42); // fails in ~vector(), `undefined` is undefined
}
为了解决此问题,我通常会在实现类的文件中添加一个默认析构函数作为锚点,该类可以访问定义。
struct undefined;
struct S
{
S(int);
~S(); // implemented in another TU as `S::~S() = default;`
std::vector<undefined> v;
};
int main()
{
S s(42); // ok
}
我正在尝试对继承基类构造函数的派生类做同样的事情:
#include <vector>
struct undefined;
struct base
{
base(int);
};
struct derived : base
{
using base::base;
~derived();
std::vector<undefined> v;
};
int main()
{
derived d(1); // fails in ~vector(), `undefined` is undefined
}
如果我更改为不继承构造函数,则编译成功:derived
#include <vector>
struct undefined;
struct base
{
base(int);
};
struct derived : base
{
//using base::base; <--- note
derived(int);
~derived();
std::vector<undefined> v;
};
int main()
{
derived d(1); // ok
}
真正让我感到困惑的是,clang 是这样说的:
stl_vector.h:336:35: error: arithmetic on a pointer to an incomplete type 'undefined'
[blah]
a.cpp:12:14: note: in instantiation of member function 'std::vector<undefined>::~vector' requested here
using base::base;
^
听起来也带来了一些破坏的东西,但我不知道是什么。我尝试删除两个类中的复制/移动构造函数/运算符,但错误仍然存在。g++ 和 Visual C++ 都失败,并出现类似的错误。using base::base
vector
这是怎么回事?
答:
我认为您应该提供通过头文件的定义,或者用作折衷方案。struct undefined
std::vector<undefined*>
错误消息可能与 std::vector 的实现有关 - 尽管我不能保证,因为我没有阅读它。例如,模板类 T 的 operator[](int idx) 方法的实现可能如下所示:stl_vector.h:336:35: error: arithmetic on a pointer to an incomplete type 'undefined'
return *((T*)(array_base_ptr + idx * sizeof(T)))
显然,需要知道模板类 T 的定义才能正确实例化模板代码(否则无法返回正确的大小)。请记住,STL 是标准的模板库,因此在编译时需要知道 STL 的定义。sizeof
T
在不定义类的情况下使用不是问题,因为指针具有固定大小。J.F. 始终是指针的大小 - 在 64 位平台上为 8 个字节。std::vector<undefined*>
undefined
sizeof(T)
评论
std::vector
具有名为 allocator 的第二种类型参数,其默认值为:
template<typename T, typename alloc=std::allocator<T>
class std::vector;
第二种类型在 的每个构造函数中实例化。如果分配器不是空类型(具有非静态数据成员),则最终在 的布局中组合分配器的一个实例。默认的 allocator() 需要知道元素的大小和构造函数签名。当类构造函数的声明(但不存在其定义)存在时,不会调用构造函数和分配器函数;因此,不需要元素类型是完整的。
如果继承构造函数是一种需求,请将向量包装在一个框中,该框的析构函数和构造函数在与类相同的 TU 中定义:std::vector
std::vector
std::allocator<T>
std::vector
struct derived:base{
using base::base;
struct box_t{
box_t();
box_t(box_t const&);
box_t(box_t&&);
~box_t();
auto& operator=(box_t const&);
auto& operator=(box_t &&);
std::vector<fwd_element_t> vec;
} box;
};
在定义的同一 TU 中,您需要将成员定义为:fwd_element_t
box_t
default
auto& derived::box_t::operator(box_t&&)=default;
//Define the rest just the same
您也可以以类似的方式使用 PIMPL。但这一次,你只需要正确定义。std:: unique_ptr<fwd_element_t[], custom_deleter>
void custom_deleter::operator()(fwd_element_t *)
或者尝试使用和定义 的每个成员。但我不确定最后一种选择。std::vector<fwd_element_t, custom_alloc>
custom_alloc
评论
std::allocator<undefined>
vector<T>
unique_ptr<T[]>
vector
unique_ptr
评论
vector<T>
必须知道其元素的大小,而这不能从不完整的类型中确定。可以有一个指向不完整类型的指针向量。vector
derived(int v) : base(v) = default;
struct S
undefined
S