且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在现代C ++中将不同类类型的对象存储到一个容器中?

更新时间:2022-12-29 08:36:19

您可以将不同的对象类型存储在 std :: variant 中.如果这样做,则无需具有通用接口并使用虚函数.

You can store different object types in a std::variant. If you do so, there is no need to have a common interface and use virtual functions.

示例:

class A
{
    public:
        void DoSomething() { std::cout << "DoSomething from A" << std::endl; }
};

class B
{
    public:
        void DoSomething() { std::cout << "DoSomething from B" << std::endl; }
};

int main()
{
    std::vector< std::variant< A, B > > objects;

    objects.push_back( A{} );
    objects.push_back( B{} );

    for ( auto& obj: objects )
    {
        std::visit( [](auto& object ){ object.DoSomething(); }, obj);
    }
}

但是使用此解决方案可以也有缺点.通过 std :: visit 访问可能很慢.有时在这种情况下,gcc会生成非常糟糕的代码.(跳转表是在运行时生成的,不知道为什么!).您总是通过表访问来调用该函数,这需要花费额外的时间.将对象存储在 std :: variant 中始终会占用变体中最大类的大小,此外,变体内部的tag变量还需要一些空间.

But using this solutions can have also drawbacks. Access via std::visit may be slow. Sometimes e.g. gcc generates very bad code in such situations. ( jump table is generated in runtime, no idea why! ). You always call the function via table access which takes some additional time. And storing the objects in std::variant consumes always the size of the biggest class you have in the variant and in addition you need some space for the tag variable inside the variant.

旧"方法是将原始或更高级的智能指针存储到向量中,并通过基本指针简单地调用通用接口函数.这里的缺点是每个实例中都有额外的vtable指针(通常与std :: variant中的tag变量大小相同).具有vtable访问权限的间接调用函数也要付出(小的)代价.

The "old" way is to store raw or better smart-pointers into the vector and simply call via base pointer the common interface functions. The drawback here is the additional vtable pointer in each instance ( which is typically the same size as the tag variable in the std::variant ). The indirection with vtable access to call the function comes also with a ( small ) cost.

具有基本类型和向量的智能指针的示例:

Example with smart pointer of base type and vector:

class Interface
{
    public:
        virtual void DoSomething() = 0;
        virtual ~Interface() = default;
};

class A: public Interface
{
    public:
        void DoSomething() override { std::cout << "DoSomething from A" << std::endl; }
        virtual ~A(){ std::cout << "Destructor called for A" << std::endl; }
};

class B: public Interface
{
    public:
        void DoSomething() override { std::cout << "DoSomething from B" << std::endl; }
        virtual ~B(){ std::cout << "Destructor called for B" << std::endl; }
};

int main()
{
    std::vector< std::shared_ptr<Interface>> pointers;

    pointers.emplace_back( std::make_shared<A>() );
    pointers.emplace_back( std::make_shared<B>() );

    for ( auto& ptr: pointers )
    {
        ptr->DoSomething();
    }
}

如果您足够使用 std :: unique_ptr ,则可以使用该代码.这取决于是否需要在设计中传递指针.

If std::unique_ptr is sufficient for you, you can use that one. It depends on the need of passing pointers around or not in your design.

提示:如果您使用的是指向基类类型的指针,请不要忘记将析构函数设为虚拟!另请参阅:何时使用虚拟析构函数

Hint: If you are using pointers to base class type never forget to make your destructors virtual! See also: When to use virtual destructors

在您的情况下,我将投票决定在简单向量中使用基类类型的智能指针!

In your case I would vote to use smart-pointers of base class type in simple vector!

顺便说一句:

virtual auto ObjType(void) -> TYPES

对我来说这看起来很丑!此处不需要 auto ,因为在编写函数参数列表之前,已知返回类型.在这种情况下,需要推导模板参数来定义返回类型,这是必需的,但是这里不是!请不要使用总是自动!

That look ugly to me! No need for auto here as the return type is known before you write the function parameter list. In such cases, where template parameters are need to be deduced to define the return type, it is needed, but not here! Please do not use always auto!