【问题标题】:Is it permissible for standard library implementation to have class definition that is different from the C++ standard?标准库实现是否允许具有与 C++ 标准不同的类定义?
【发布时间】:2016-10-27 07:40:00
【问题描述】:

以下代码使用 clang 和 MSVC 成功编译,但在 GCC 6.1.0 中编译失败。

#include <memory>

template<typename R, typename T, typename... Args>
T* test(R(T::*)(Args...) const)
{
    return nullptr;
}

int main()
{
    using T = std::shared_ptr<int>;
    T* p = test(&T::get);
}

带有以下错误信息

prog.cc: In function 'int main()':
prog.cc:13:16: error: invalid conversion from 'std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2u>*' to 'T* {aka std::shared_ptr<int>*}' [-fpermissive]
     T* p = test(&T::get);
            ~~~~^~~~~~~~~

问题在于 libstdc++ 通过从基类 std::__shared_ptr 继承成员函数 get 来实现 std::shared_ptr。

在C++标准20.8.2.2类模板shared_ptr中,它指定了std::shared_ptr类的类定义以及该类的所有成员函数。

我的问题是,实现是否必须至少在标准类内部提供标准中定义的所有公共类成员?是否允许通过从libstdc++中实现的基类继承来提供成员函数?

【问题讨论】:

  • 17.6.5.11 派生类 [派生] 实现可以从一个为实现保留名称的类派生 C++ 标准库中的任何类。脚注 188 (n3797) 可能也很有趣。
  • Hmya,您也不会在标准中找到 Lock_policy 模板参数。当编译器库编写者在标准获得批准之前实现一个类或使其适用于现实世界时,就会发生这种情况。在标准中不包括锁定策略是一个颇具争议的决定。现实世界覆盖。
  • 我不太擅长阅读标准。我对这句话的理解是允许实现从基类继承,但没有指定是否允许/不允许通过从基类继承来提供标准类中的类成员函数。
  • 问题是编译器出错了。 std::shared_ptr&lt;int&gt;::get 是std::shared_ptr&lt;Int&gt; 的成员函数,不管它是如何到达那里的,而模板中的T 是std::shared_ptr&lt;int&gt;。
  • 我认为实现提供额外的构造函数或方法以及实现从隐藏类派生是可以的,但实现的标准类至少应该具有标准中定义的所有公共成员。如果标准说shared_ptr 类定义中有一个get 成员,我可以期望正确的行为能够编译上面的代码吗?

标签: c++ language-lawyer c++-standard-library


【解决方案1】:

标准对类型及其成员的规范是规范性文本,除非它明确另有说明。因此,需要一个实现来遵循……在某种程度上,一个实现需要遵循标准中的任何东西。

这个范围就是“好像”规则。也就是说,只要类型表现得“好像”它按照指定的方式完成,实现就可以做它想做的事情。该标准具有特定语言说明类型可以从任意的、实现提供的基类派生的原因是因为这是用户可以检测到的东西。它是可见的行为(通过隐式转换等),因此标准必须做出例外以允许它。

继承成员几乎与在主类中声明成员相同。事实上,我所知道的唯一区别方法是做你在这里所做的:使用模板参数推导规则。即使您可以将成员指定为Derived::get,但如果它确实来自某个基类,编译器会知道。

然而,[member.functions] 在这里拯救了 GCC。它具有显式语言,允许标准库实现向类添加额外的重载。因此,您在此处使用 std::shared_ptr&lt;int&gt;::get 不是明确定义的行为。事实上,脚注 187 澄清了这一点:

因此,C++ 标准库中类的成员函数的地址具有未指定的类型。

这只是一个脚注,但意图似乎很明确:您不能依赖任何特定的实现来返回任何特定类型的成员指针。即使您对正确的签名应用了强制转换操作,也不能保证它会起作用。

因此,虽然标准库中的类定义是规范文本,但 [member.functions] 清楚地表明,唯一 您可以保证这些定义是您可以使用提供的论据。其他任何事情,比如获取成员指针,都是实现定义的。

【讨论】:

  • 我认为脚注 187 应该在 [member.function] 2 和 (2.1) 的上下文中阅读。 2. 实现可以在类中声明额外的非虚拟成员函数签名:(2.1) — 通过向成员函数签名添加具有默认值的参数;187 我的理解是实现可以提供成员功能void get(int = 0);如果 std 指定成员函数 void get() 因此脚注 187 阐明未指定成员 get 的类型。它可以是void(T::*)(int) 或void(T::*)(),但它不是必须仍然在一个类中吗?
  • 如果上面的测试函数返回sizeof(Args...),则脚注187将其定义为实现。
  • @kwanti:脚注没有说 signature 是实现定义的;它说整个类型都是。
  • 既然这是唯一的答案,我会接受它(谢谢你的回答)。令人难以置信的是,标准告诉我们不能将标准类的成员函数指针用于任何事情,因为我们不知道它的类型。我是否正确地说这意味着如果我使用 std::function 绑定到任何标准类的成员函数指针,我不能指望它是可移植的,因为类型(包括函数签名?)是实现定义的?
  • @kwanti:差不多。您始终可以使用 lambda 来创建成员指针调用的效果。
猜你喜欢
  • 2022-01-02
  • 2016-07-31
  • 2011-06-16
  • 1970-01-01
  • 1970-01-01
  • 2014-10-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多