为什么 MSVC 允许使用受保护的析构函数直接实例化对象?

问题描述 投票:0回答:1

在我正在开发的一个库中,其目的是包装 C 建模库,我有一个类型

EntityBase
,它用作其他类(如
Body
Face
等)的基类...:

class EntityBase
{
   public:

      // default ctor
      EntityBase() = default;

      // constructing from native handle
      explicit constexpr EntityBase(int handle) noexcept
         : m_handle{handle}{}

      [[nodiscard]] constexpr int handle() const noexcept { return m_handle; }

      // ... operations common among different entity types ...

   protected:

      // protected destructor to prevent deletion from pointer to base
      ~EntityBase() = default;

   private:

      int m_handle{0};
};

class Face : public EntityBase
{
   public:

      Face() = default;

      explicit constexpr Face(int handle) noexcept
         : EntityBase{handle}{}

      // ... operations specific for a face ...

   // IMPORTANT: no additional member is added to the Face class (and will never be by design)
};

// ... other classes (Body, Edge, etc...) ...

一个重要的信息是

EntityBase
及其派生类具有相同的大小,即
EntityBase
所包装的整数句柄的大小。 这是因为,根据设计,我希望这些代表强类型句柄的类型被传递到底层 C 库,从而强制类型安全,但没有开销。 因此,每种类型都有其语义(主体、面、边缘等),但某些操作是通用的(这就是基类存在的原因)。

EntityBase
not有一个virtual析构函数,以避免vtable的开销,并维护库类型的值语义。

因此,析构函数被声明为 protected 以防止有人从指向

EntityBase
的指针中删除派生类。

我发现,奇怪的是,MSVC 允许直接实例化

EntityBase
的实例:

consteval int test() noexcept
{
    EntityBase e{42};
    return e.handle();
}

int main()
{
    Face f{ 42 };

    EntityBase eb1;      // compiles on MSVC
    EntityBase eb2{ 1 }; // compiles on MSVC
    EntityBase eb3{ f }; // compiles on MSVC

    constexpr int value = test();

    return eb1.handle() + eb2.handle() + eb3.handle() + value; // returns 85 on MSVC 
}

相同的代码反而被 GCC 和 Clang 拒绝。

<source>: In function 'int main()':
<source>:40:16: error: 'constexpr EntityBase::~EntityBase()' is protected within this context
   40 |     EntityBase eb1;
      |                ^~~
<source>:19:5: note: declared protected here
   19 |     ~EntityBase() = default;
      |     ^
<source>:41:23: error: 'constexpr EntityBase::~EntityBase()' is protected within this context
   41 |     EntityBase eb2{ 1 };
      |                       ^
<source>:19:5: note: declared protected here
   19 |     ~EntityBase() = default;
      |     ^
<source>:42:23: error: 'constexpr EntityBase::~EntityBase()' is protected within this context
   42 |     EntityBase eb3{ f };
      |                       ^
<source>:19:5: note: declared protected here
   19 |     ~EntityBase() = default;
      |     ^
Compiler returned: 1

请注意,如果我尝试从指向

Face
的指针中删除
EntityBase
,MSVC(正确地)不会编译。此外,一旦我向派生类添加数据成员,MSVC 就开始拒绝
EntityBase
的直接实例化。

看来,当且仅当基类型与派生类型具有相同的大小时,MSVC 才允许直接实例化基类型,即使它具有受保护的析构函数。 此外,MSVC 甚至在

EntityBase
函数中也允许实例化
consteval

因此我的问题是:

  • 这里谁是正确的:MSVC 还是 Clang/GCC?
  • 如果
    Face
    没有定义任何其他数据成员并且它是可轻易破坏的,那么通过指向
    EntityBase
    的指针删除
    Face
    仍然是 UB 吗?

感谢任何试图澄清这种情况的人。

代码位于编译器资源管理器上:https://godbolt.org/z/njfPT5aqT

c++ destructor portability
1个回答
0
投票

这里谁是正确的:MSVC 还是 Clang/GCC?

MSVC显然不符合标准。

如果 Face 没有定义任何其他数据成员并且它是可轻易破坏的,那么通过指向 EntityBase 的指针删除 Face 仍然是 UB 吗?

是的,唯一重要的是基础析构函数是否是

virtual
。它不在这里,所以它是UB。

© www.soinside.com 2019 - 2024. All rights reserved.