C++/编译:是否可以设置vptr的大小(全局vtable+;2字节索引) 我最近发布了一个关于C++中虚拟性造成内存开销的问题。这些答案让我了解vtable和vptr是如何工作的。 我的问题是:我在超级计算机上工作,我有几十亿个对象,因此我不得不考虑虚拟性带来的内存开销。经过一些度量之后,当我使用带有虚拟函数的类时,每个派生对象都有其8字节的vptr。这是不可忽视的

C++/编译:是否可以设置vptr的大小(全局vtable+;2字节索引) 我最近发布了一个关于C++中虚拟性造成内存开销的问题。这些答案让我了解vtable和vptr是如何工作的。 我的问题是:我在超级计算机上工作,我有几十亿个对象,因此我不得不考虑虚拟性带来的内存开销。经过一些度量之后,当我使用带有虚拟函数的类时,每个派生对象都有其8字节的vptr。这是不可忽视的,c++,compilation,g++,intel,vptr,C++,Compilation,G++,Intel,Vptr,我想知道intel icpc或g++是否有一些配置/选项/参数,以使用精度可调的“全局”vtables和索引,而不是vptr。因为这样可以让我对数十亿个对象使用2字节索引(unsigned short int)而不是8字节的vptr(这大大减少了内存开销)。有没有什么方法可以通过编译选项实现这一点(或类似的事情) 非常感谢。不幸的是。。。不是自动的 但请记住,v表只是运行时多态性的语法糖。如果你愿意重新设计你的代码,有几种选择 外部多态性 手工制作的v形桌 手工制作的多态性 1)外部多态性 其

我想知道intel icpc或g++是否有一些配置/选项/参数,以使用精度可调的“全局”vtables和索引,而不是vptr。因为这样可以让我对数十亿个对象使用2字节索引(unsigned short int)而不是8字节的vptr(这大大减少了内存开销)。有没有什么方法可以通过编译选项实现这一点(或类似的事情)


非常感谢。

不幸的是。。。不是自动的

但请记住,v表只是运行时多态性的语法糖。如果你愿意重新设计你的代码,有几种选择

  • 外部多态性
  • 手工制作的v形桌
  • 手工制作的多态性

  • 1)外部多态性

    其思想是,有时您只需要暂时的多态性。例如:

    std::vector<Cat> cats;
    std::vector<Dog> dogs;
    std::vector<Ostrich> ostriches;
    
    void dosomething(Animal const& a);
    
    桥梁是一劳永逸的:

    template <typename T>
    class AnimalT: public Animal {
    public:
        AnimalT(T& r): _ref(r) {}
    
        virtual size_t age() const override { return _ref.age(); }
        virtual size_t weight() const { return _ref.weight(); }
    
        virtual void eat(Food const& f) override { _ref.eat(f); }
        virtual void sleep(Duration const d) override { _ref.sleep(d); }
    
    private:
        T& _ref;
    };
    
    template <typename T>
    AnimalT<T> iface_animal(T& r) { return AnimalT<T>(r); }
    
    它会为每个项目带来两个指针的开销,但只要您需要多态性就行

    另一种方法是让
    AnimalT
    也处理值(而不是引用),并提供
    clone
    方法,允许您根据情况在是否具有v指针之间进行完全选择

    在这种情况下,我建议使用一个简单的类:

    template <typename T> struct ref { ref(T& t): _ref(t); T& _ref; };
    
    template <typename T>
    T& deref(T& r) { return r; }
    
    template <typename T>
    T& deref(ref<T> const& r) { return r._ref; }
    
    然后为锚定在
    Foo
    中的层次结构提供全局数组:

    extern FooVTable const* const FooVTableFoo;
    extern FooVTable const* const FooVTableBar;
    
    FooVTable const* const FooVTables[] = { FooVTableFoo, FooVTableBar };
    
    enum class FooVTableIndex: unsigned short {
        Foo,
        Bar
    };
    
    然后,在
    Foo
    类中,您只需保留最派生的类型:

    class Foo {
    public:
    
        void dofunc(int i, int j) {
            (this->*(table()->_do))(i, j);
        }
    
    protected:
        FooVTable const* table() const { return FooVTables[_vindex]; }
    
    private:
        FooVTableIndex _vindex;
    };
    
    之所以存在封闭的层次结构,是因为
    FooVTables
    数组和
    FooVTableIndex
    枚举需要知道层次结构的所有类型

    但是,可以绕过枚举索引,通过使数组非常量,可以预先初始化为更大的大小,然后在init处使每个派生类型自动在那里注册。因此,在初始化阶段会检测到索引冲突,甚至可以进行自动解析(扫描阵列以查找空闲插槽)

    这可能不太方便,但确实提供了一种打开层次结构的方法。显然,在任何线程启动之前编写代码都比较容易,因为我们在这里讨论的是全局变量


    3)手工制作的多态性

    (仅适用于封闭层次结构)

    后者基于我探索LLVM/Clang代码库的经验。编译器面临的问题与您所面临的问题完全相同:对于成千上万个小项目,每个项目的vpointer确实会增加内存消耗,这很烦人

    因此,他们采取了一种简单的方法:

    • 每个类层次结构都有一个对应的
      enum
      列表,列出所有成员
    • 层次结构中的每个类在构造时都会将其对应的
      枚举数传递给它的基
    • 虚拟性是通过切换
      enum
      并适当地强制转换来实现的
    代码:

    enum class FooType { Foo, Bar, Bor };
    
    class Foo {
    public:
        int dodispatcher() {
            switch(_type) {
            case FooType::Foo:
                return static_cast<Foo&>(*this).dosomething();
    
            case FooType::Bar:
                return static_cast<Bar&>(*this).dosomething();
    
            case FooType::Bor:
                return static_cast<Bor&>(*this).dosomething();
            }
            assert(0 && "Should never get there");
        }
    private:
        FooType _type;
    };
    
    然后你会:

     void Foo::dodispatcher() {
         switch(_type) {
     #   define ACT_ON(X) case FooType::X: return static_cast<X&>(*this).dosomething();
    
     #   include "FooList.inc"
    
     #   undef ACT_ON
         }
    
         assert(0 && "Should never get there");
     }
    
    void Foo::dodispatcher(){
    开关(U型){
    #定义ACT_ON(X)case FooType::X:返回静态_cast(*this).dosomething();
    #包括“傻瓜公司”
    #未定义的行为
    }
    断言(0&“不应该到达那里”);
    }
    
    Chris Lattner评论说,由于交换机是如何生成的(使用代码偏移量表),这会生成类似于虚拟调度的代码,因此CPU开销大致相同,但内存开销较低

    显然,一个缺点是
    Foo.cpp
    需要包含其派生类的所有头。这有效地封住了等级制度


    我自愿提出了从最开放到最封闭的解决方案。它们具有不同程度的复杂性/灵活性,由您选择最适合您的


    重要的是,在后两种情况下,销毁和复制需要特别小心。

    问得好。我几乎可以肯定答案是“不”。也许你可以使用模板来摆脱运行时多态性。你真的需要64位的地址空间,还是可以编译32位的可执行文件?这将把vtable指针切成两半。事实上,夏普顿有最好的答案。另一种方法是将数据移出对象,但这可能不实际。@Ben“你不能真正容纳”数十亿个对象“在32位地址空间中。您真的需要多态类吗?”?也许重新设计代码会比试图修改虚拟函数的工作方式更能提高效率。@Kerrek:毫无疑问,这会——如果编译器原则上可以自动完成这项工作,那么程序员可以通过将所有虚拟函数替换为函数指针表中的项来手动完成,将所有虚拟调用替换为通过此类表间接调用,并将正确表的索引放在每个对象的开头。但是如果有一个
    gcc
    选项可以这样做,我当然会对makefile进行一行修改,而不是为(1)重新设计一个大的代码库:-),您可能会想查看
    std::ref
    @KerrekSB:谢谢,但是
    std::reference_wrapper
    是一个非常好的选择:xHow#3节省空间?
    private:FooType\u-type占用的空间与vptr差不多?@Yay295:在C++03中,
    enum
    的大小留给编译
    extern FooVTable const* const FooVTableFoo;
    extern FooVTable const* const FooVTableBar;
    
    FooVTable const* const FooVTables[] = { FooVTableFoo, FooVTableBar };
    
    enum class FooVTableIndex: unsigned short {
        Foo,
        Bar
    };
    
    class Foo {
    public:
    
        void dofunc(int i, int j) {
            (this->*(table()->_do))(i, j);
        }
    
    protected:
        FooVTable const* table() const { return FooVTables[_vindex]; }
    
    private:
        FooVTableIndex _vindex;
    };
    
    enum class FooType { Foo, Bar, Bor };
    
    class Foo {
    public:
        int dodispatcher() {
            switch(_type) {
            case FooType::Foo:
                return static_cast<Foo&>(*this).dosomething();
    
            case FooType::Bar:
                return static_cast<Bar&>(*this).dosomething();
    
            case FooType::Bor:
                return static_cast<Bor&>(*this).dosomething();
            }
            assert(0 && "Should never get there");
        }
    private:
        FooType _type;
    };
    
     // FooList.inc
     ACT_ON(Foo)
     ACT_ON(Bar)
     ACT_ON(Bor)
    
     void Foo::dodispatcher() {
         switch(_type) {
     #   define ACT_ON(X) case FooType::X: return static_cast<X&>(*this).dosomething();
    
     #   include "FooList.inc"
    
     #   undef ACT_ON
         }
    
         assert(0 && "Should never get there");
     }