【问题标题】:Non virtual version of virtual class虚拟类的非虚拟版本
【发布时间】:2011-09-26 02:35:42
【问题描述】:

假设我们有以下两个类定义。

#include <iostream>
#include <array>

class A
{
public:
  virtual void f() = 0;
};

class B : public A
{
public:
  virtual void f() { std::cout << i << std::endl; }
  int i;
};

这里sizeof(B) == 8,大概是4个虚拟指针和4个int

现在假设我们创建一个 B 数组,如下所示:

std::array<B, 10> x;

现在我们得到sizeof(x) == 80

如果我的理解是正确的,所有对x 元素的方法调用都是静态解析的,因为我们在编译时就知道类型。除非我们执行A* p = &amp;x[i] 之类的操作,否则我认为甚至不需要存储虚拟指针。

如果你知道它不会被使用,有没有办法在没有虚拟指针的情况下创建 B 类型的对象?

即一个模板类型nonvirtual&lt;T&gt;,它不包含虚拟指针,并且不能被T的子类型指向?

【问题讨论】:

  • 你为什么想要一个?如果您使用虚函数等从A 派生B,那么您一定有一些理由这样做。您显然希望使用A 的代码将B 视为As。那么你会从中得到什么?
  • 你为什么要这个?你真的会从剥离虚拟指针中受益吗?如果你愿意,你应该考虑创建另一个非虚拟的B 类。
  • @Dani:我希望能够同时使用同质和非同质集合,同时不为我在同质集合的情况下不使用的东西付费。
  • @Clinton 如果一个类中的单个指针被认为不止一个int 是一个重大损失,您应该阅读一些关于优化的文章。如果您仍然不相信,请在汇编中制作您的项目,您不会相信它需要多少内存(和运行时间)。

标签: c++ virtual c++11


【解决方案1】:

如果你知道它不会被使用,有没有办法在没有虚拟指针的情况下创建 B 类型的对象?

没有。对象就是它们。虚拟对象始终是虚拟的。

毕竟,你可以这样做:

A *a = &x[2];
a->f();

这是完全合法且合法的代码。 C++ 必须允许它。 B 类型是虚拟的,它有一定的大小。您不能根据使用的位置使类型成为不同的类型。

【讨论】:

  • 我知道B 是这样的,但是将nonvirtual&lt;B&gt; 定义为一个全新的类型呢?这可行吗?
  • @Clinton 否。模板无法做到这一点,因为它们无法将所有可能的功能转发到相关对象。即使他们可以,他们也必须创建一个B 来保存该对象的数据。而B 是虚拟的。
  • @Nicol Bolas:如果没有编译器的干预,就无法完成一半的 STL。他问是否存在这样的编译器干预来满足他的需求。
  • @Dani 那是 STL 的哪一半?如果没有一些编译器支持,某些元编程特征很难实现。但是有很多 STL 类型和算法的跨平台实现。不,没有“编译器干预”会导致B 类型的一种特定用途与B 的另一种用途不同。
  • @Dani typeid 归入 C++ 库规范的一部分,专门称为“语言支持库”。 move 只不过是一个花哨的演员表;它需要零编译器支持。此外,typeid 远非“STL 的一半”。事实上,“语言支持库”部分相当小而短。
【解决方案2】:

在这里回答我自己的问题,但我发现以下内容可以完成这项工作,方法是将 A 拆分为它的虚拟和非虚拟组件:

enum is_virtual
{
  VIRTUAL,
  STATIC
};

template <is_virtual X>
class A;

template<>
class A<STATIC>
{
};

template<>
class A<VIRTUAL> : public A<STATIC>
{
public:
  virtual void f() = 0;
  virtual ~A() {}
};

template <is_virtual X>
class B : public A<X>
{
public:
  void f() { std::cout << i << std::endl; }
  int i;
};

这里重要的是在B&lt;&gt; 中不要将f() 指定为虚拟。这样,如果类继承A&lt;VIRTUAL&gt;,它将是虚拟的,但如果它继承A&lt;STATIC&gt;,则不是虚拟的。然后我们可以执行以下操作:

int main()
{
  std::cout << sizeof(B<STATIC>) << std::endl; // 4
  std::cout << sizeof(B<VIRTUAL>) << std::endl; // 8
  std::array<B<STATIC>, 10> x1;
  std::array<B<VIRTUAL>, 10> x2;
  std::cout << sizeof(x1) << std::endl; // 40
  std::cout << sizeof(x2) << std::endl; // 80
}

【讨论】:

  • 这与定义 B 的 2 个版本(一个继承 A,另一个不继承 A)有何不同?
  • @David:如果 A 有非纯虚方法(和数据),那么不继承 A 将导致这些不可用。
  • 有趣。我同意这是对原始问题的某些受限版本的解决方案。
【解决方案3】:

这将是一个不错的选择,但我想不出任何方法来撤销虚拟成员或避免存储虚拟指针。

您可能会做一些讨厌的黑客攻击,方法是保留一个没有虚拟指针的 B 大小的缓冲区,并使用强制转换等。但都是未定义的行为,并且依赖于平台。

【讨论】:

    【解决方案4】:

    不幸的是,它在任何正常意义上都不起作用,因为方法调用中的代码期望虚拟指针位于类定义中。

    我想您可以将 A 和 B 的所有代码复制/粘贴到一个新类中,但这很快就会成为一个令人头疼的维护问题。

    【讨论】:

      猜你喜欢
      • 2016-10-26
      • 2019-02-21
      • 2016-05-20
      • 2012-09-27
      • 2014-05-12
      • 1970-01-01
      • 1970-01-01
      • 2015-06-13
      • 2013-05-01
      相关资源
      最近更新 更多