std::vector、不完整的类型和继承的构造函数

std::vector, incomplete type and inherited constructors

提问人:isanae 提问时间:8/19/2023 最后编辑:isanae 更新时间:9/8/2023 访问量:170

问:

我有一个带有成员变量的类,该变量已声明但未定义。如果未定义类的析构函数,则可能会出现问题,因为编译器可能会选择其他翻译单元来生成析构函数。如果该 TU 没有 的定义,则编译失败。vectorTT

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::basevector

这是怎么回事?

C++ 继承 析构函数

评论

1赞 doug 8/19/2023
vector<T>必须知道其元素的大小,而这不能从不完整的类型中确定。可以有一个指向不完整类型的指针向量。
0赞 isanae 8/19/2023
@doug绝对可以使用不完整的类型,只要你不对它做任何事情,看这个这个vector
0赞 doug 8/19/2023
好的,从 c++17 开始,您似乎可以拥有不完整的类型,只要您在类型完成之前不使用它。有趣。谢谢你的链接。
7赞 Raymond Chen 8/19/2023
继承的构造函数就像你写的一样(语法无效,但我希望你能理解) - 这意味着编译器必须为构造函数生成代码,这意味着还要为向量的析构函数生成代码,以防构造函数抛出异常。derived(int v) : base(v) = default;
1赞 isanae 8/21/2023
@fjs 没有什么能阻止我,但它是在另一个标题中定义的,里面有一堆东西,我宁愿不包含它以减少编译时间。的用户从来不需要对自身进行定义,它由内部使用。struct SundefinedS

答:

-1赞 fjs 8/19/2023 #1

我认为您应该提供通过头文件的定义,或者用作折衷方案。struct undefinedstd::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 的定义。sizeofT

在不定义类的情况下使用不是问题,因为指针具有固定大小。J.F. 始终是指针的大小 - 在 64 位平台上为 8 个字节。std::vector<undefined*>undefinedsizeof(T)

评论

1赞 wohlstad 8/19/2023
请参阅OP在问题下方的评论中发布的链接。我认为使用指针不是他们寻找的解决方案。
-1赞 Red.Wave 8/19/2023 #2

std::vector具有名为 allocator 的第二种类型参数,其默认值为:

template<typename T, typename alloc=std::allocator<T>
class std::vector;

第二种类型在 的每个构造函数中实例化。如果分配器不是空类型(具有非静态数据成员),则最终在 的布局中组合分配器的一个实例。默认的 allocator() 需要知道元素的大小和构造函数签名。当类构造函数的声明(但不存在其定义)存在时,不会调用构造函数和分配器函数;因此,不需要元素类型是完整的。 如果继承构造函数是一种需求,请将向量包装在一个框中,该框的析构函数和构造函数在与类相同的 TU 中定义:std::vectorstd::vectorstd::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_tbox_tdefault

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

评论

0赞 isanae 8/20/2023
我认为这个答案大部分都是不正确的。实例化编译良好,并且不做任何需要定义的事情。您用 a 替换 a 的建议也不是很有用。至于创建我自己的分配器,这没有区别,因为分配器与此无关。std::allocator<undefined>vector<T>unique_ptr<T[]>
0赞 Red.Wave 8/20/2023
@isanae调用分配器成员的每个构造函数和大多数其他成员。构造函数的具体情况实际上不需要访问成员,除非通过分配器。默认实现是 template,因此是内联的,并尝试调用您尚未提供的元素构造函数。事实上,分配器解决方案也支持成员函数的内联定义。使用是 PIMPL 成语的标准做法。显然,您正在尝试隐藏元素的实现;因此,PIMPL是主要解决方案。否则,为什么要隐藏元素的布局?vectorunique_ptr