【问题标题】:Size of derived class in virtual base class function虚拟基类函数中派生类的大小
【发布时间】:2020-09-30 07:29:47
【问题描述】:

考虑下面的代码

class A {
    int x, y;
public:
    A(){}
    virtual void PrintSize(){ cout << sizeof(typeof(*this)) << endl; }
};

class B : public A {
    int a, b, c;
public:
    B(){}
};

int main() {
    A obja;
    B objb;

    obja.PrintSize();
    objb.PrintSize();
}

“PrintSize()”的目的是获取我们从中调用它的当前类的大小。发生的情况是 this-keyword 引用了类 A,即使我们是从 B 调用它。我们不希望这样,因为我们需要这个函数对子类是通用的。

我们显然可以为每个类逐字重新定义函数。由于有太多不必要的行,代码将变得更难处理。更不用说将函数重新编写到每个类都会破坏最初派生它的目的。

这是我的临时修复:

class A {
public:
    virtual void PrintSize(){ cout << sizeof(typeof(*this)) << endl; }
};

class B : public A {
public:
    virtual void PrintSize(){ cout << sizeof(typeof(*this)) << endl; }
};

class C : public A {
public:
    virtual void PrintSize(){ cout << sizeof(typeof(*this)) << endl; }
};

class D : public A {
public:
    virtual void PrintSize(){ cout << sizeof(typeof(*this)) << endl; }
};

【问题讨论】:

  • 什么是typeof
  • 我不认为 typeof 是 C++ 中的东西。我相信这是一个 GCC extension
  • 哦,我不知道这一点。我一直用 gcc 编程,所以我认为这是一个 c++ 的东西。

标签: c++ class inheritance gcc typeof


【解决方案1】:

虽然接受的答案可能已经解决了眼前的问题,但您突然之间没有 BC 的公共基类。它们继承自两个不相关的类,即A&lt;B&gt;A&lt;C&gt;

另一种方法是创建一个定义接口的公共基础(下面称为Interface),并在派生类和接口之间添加CRTP 类模板。这使您可以保留对Interface 的指针和引用,并使用它们调用virtual 成员函数。

这是一个在vector 中存储指向公共基类的指针的示例:

#include <iostream>
#include <memory>
#include <vector>

struct Interface {
    virtual ~Interface() = default;

    virtual void PrintSize() const = 0;
    virtual void do_stuff() const = 0;
};

template<typename T>
struct Printer : public Interface {
    void PrintSize() const override {
        std::cout << sizeof(T) << '\n';
    }
};

class B : public Printer<B> {
    int a{};
public:
    void do_stuff() const override { std::cout << "B doing stuff\n"; }
};

class C : public Printer<C> {
    int a{}, b{}, c{};
public:
    void do_stuff() const override { std::cout << "C doing stuff\n"; }    
};

int main() {
    std::vector<std::unique_ptr<Interface>> objs;

    objs.emplace_back(std::make_unique<B>());
    objs.emplace_back(std::make_unique<C>());

    for(auto& ptr : objs) {
        ptr->do_stuff();
        ptr->PrintSize();
    }
}

可能的输出:

B doing stuff
16
C doing stuff
24

【讨论】:

  • 你特意写“int a{}”而不是“int a”是有原因的。还是它们有同样的效果?
  • @Trad_NotSuitedPC int a{} 初始化 a(在这种情况下将其设置为 0),而将其保留为 int a 没有。读取未初始化的变量会导致未定义的行为,因此当没有构造函数执行此操作时,不要创建具有未初始化成员的对象,默认初始化通常是好的。您可以尝试删除 {} in this example 并查看编译器警告。
  • 构造函数不是用来初始化成员变量的吗?构造函数不会隐式初始化这些变量(在我上面的示例中使用A(){}
  • @Trad_NotSuitedPC 否,但如果您实现构造函数,通常会显式初始化成员:A() : a{} { std::cout &lt;&lt; "constructor body\n"; } - 或设置特定值:A() : a{10} { ... }
  • @Trad_NotSuitedPC 您可以看到未初始化成员here 的可能令人惊讶的结果。当您读取未初始化的成员时发生的确切情况是未定义的。在这个例子中,它打印数字 10 before 它甚至被设置,然后它打印另一个数字。它也可能崩溃和燃烧。
【解决方案2】:

您可以使用 CRTP 习语来执行此操作。 https://eli.thegreenplace.net/2011/05/17/the-curiously-recurring-template-pattern-in-c 这个想法是父类是一个模板,因此您可以直接在其中访问子类的类型。 这样,您就可以从子类中删除所有“PrintSize”。

例子:

template <typename Derived>
class A {
    int x, y;
public:
    A() {}
    void PrintSize() { cout << sizeof(Derived) << endl; }
};

class B : public A<B> {
    int a, b, c;
public:
    B() {}
};

class C : public A<C> {
public:
    C() {}
};

int main() {
    C objc;
    B objb;

    objc.PrintSize();
    objb.PrintSize();
}

输出是:

8

20

【讨论】:

  • 应该注意的是,对于 CRTP,您会失去一个通用的基类。 BC 继承自不同的类。
  • 请注意,在您的答案中提供的代码中,没有通用的基本类型。您需要一个非模板化的基类型,A 从中继承,它为void PrintSize(); 提供了一个virtual 接口然后,A 成为一个实现细节,应该由您的类型的用户直接使用,除了从它继承。
  • 是的,这正是我所需要的!很好,这也删除了 GCC 依赖项。
猜你喜欢
  • 1970-01-01
  • 2021-04-14
  • 2013-02-26
  • 2020-02-01
  • 2015-10-24
  • 2019-11-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多