【问题标题】:Optional class members without runtime overhead没有运行时开销的可选类成员
【发布时间】:2021-01-18 13:02:15
【问题描述】:

我有以下非常普遍的问题,但我还没有找到令人满意的解决方案: 所以我想要两个类AAData基本相同,只是后者有一个附加属性data并且每个类都支持一个函数foo(),这是不同的,因为它取决于存在的附加数据。

愚蠢的解决方案是复制整个类并稍微更改它,但这会导致代码重复并且难以维护。使用 std::optional 或指针会导致额外的检查,从而导致运行时开销,对吧?

我的问题是,是否有一种方法可以获得与仅复制代码而无需实际代码复制相同的运行时性能?我目前的解决方案是让AData成为派生类,并声明为A的朋友,然后重写虚函数foo(),但由于使用friend,我不喜欢这种做法。

【问题讨论】:

  • 为什么要声明为好友?继承的重点是解决您提到的“问题”还是我遗漏了什么?也许你可以把它放在一个我们(或只有我)能更好理解的小代码示例中
  • 您没有AData 的任何原因源自A
  • 因为函数foo() 在这两种情况下都需要访问私有成员。我可能应该澄清一下。
  • 你不能保护私有成员吗?
  • 顺便说一句:使用 std::optional 或 std::variant 可能与调用 vtable 具有相同的开销。它几乎不依赖于编译器的优化。对我来说,真正的用例是什么还不清楚!使用派生类和虚方法就可以了,不需要“朋友”。

标签: c++


【解决方案1】:

您可以使用静态多态性和奇怪的重复模板模式。 AAData 都提供 foo(),但行为是特定于类的,直到 doFoo()。不使用虚拟调度也避免了 vtable 查找的运行时开销。

template <typename TData>
class Abase
{
public:
   void foo()
   {
       static_cast<TData*>(this)->doFoo();
   }
};

class A : public Abase<A>
{
    friend ABase<A>;
    void doFoo() { cout << "A::foo()\n"; }
};

class AData : public Abase<AData>
{
   friend Abase<AData>;
   int someDataMember;
   void doFoo() {  cout << "AData::foo()\n"; /*... use someDataMember ... */}
};

Live

【讨论】:

  • “同样不使用虚拟调度可以提高性能。”但是调度必须在某个地方完成。如果它使用 std::variant 也需要时间。也可以使用 if/else 手动调度。通常 vtable 调度非常快,std::variant 可以相同但不会更快。
  • @Klaus:这里的调度是在编译时完成的,所以没有运行时开销。 vtable 查找在运行时执行。
  • 我同意克劳斯的观点。要么我们知道具体类型并且不需要虚拟分派,要么我们不知道并且我们只是将该分派移到类之外进行管理。
  • 您这里没有调度。如果您存储两种类型的元素并想要调用对象的函数,则需要。此时必须进行调度。
  • 这里的“调度”是编译器只会在模板实例化时调用正确的调用。也可以内联调用。在任何情况下,都不会通过 vtable 进行运行时调度。是的,如果一个人真的想在运行时多态地处理这两个时间,就像通过基类指针访问派生类一样,那么就需要虚拟行为
【解决方案2】:

为什么不使用合成:

class A
{
public:
    void foo() { /*...*/ }
};

class AData
{
   A a;
   int someDataMember;
public:
    void foo() { /*... use someDataMember ...*/ }
};

【讨论】:

  • +1:OP 没有关于具体使用场景的任何进一步细节,除了可能是私有继承之外,组合应该是更好的设计方法。
  • 我认为它的行为与复制的类完全不同,因为 A 中使用 foo() 的函数不会被替换,或者你能以某种方式使它工作吗?也许我应该更准确地了解要求。
猜你喜欢
  • 2021-11-12
  • 1970-01-01
  • 2013-12-26
  • 1970-01-01
  • 2013-10-07
  • 1970-01-01
  • 1970-01-01
  • 2015-11-25
相关资源
最近更新 更多