如何在现代C ++中将不同类类型的对象存储到一个容器中? [英] How to store object of different class types into one container in modern c++?

查看:113
本文介绍了如何在现代C ++中将不同类类型的对象存储到一个容器中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了这个问题,我想将不同的类(共享相同的接口)存储到一个通用容器中.

I ran into this problem, where I want to store different classes (sharing same interface) into a common container.

在现代C ++中可以做到这一点吗?

Is it posssible to do that in modern C++?

当我不想将对象存储为指针时,这被允许吗?如果我必须使用指针,那么应该采用哪种推荐或更清洁的方式?

Is this allowed, when I don't want to store objects as pointers? If I have to use pointer, then what should be the recommended or cleaner way to do that?

处理此类用例的正确方法应该是什么?

What should be the correct approach to handle such usecases?

#include <iostream>
#include <string>
#include <vector>

enum class TYPES: int {TYPE1 = 0, TYPE2, INVALID};

class IObj
{
public:
    virtual auto ObjType(void) -> TYPES = 0;
};

class InterfaceObj: IObj
{
public:
    virtual auto Printable(void) -> void = 0;
};

class InterfaceTesla
{
public:
    virtual auto Creatable(void) -> void = 0;
};

class CObj: InterfaceObj
{
private:
    std::string msg;
public:
    CObj()
    {
        msg = "Elon Mask!";
    }
    virtual auto ObjType(void) -> TYPES override
    {
        return TYPES::TYPE1;
    }
    virtual auto Printable(void) -> void override
    {
        std::cout<<msg<<std::endl;
    }
};

class CObjTesla: public CObj, InterfaceTesla
{
private:
    std::string vhc;
public:
    CObjTesla()
    : CObj()
    {
        vhc = "Cybertruck";
    }
    virtual auto ObjType(void) -> TYPES override
    {
        return TYPES::TYPE2;
    }
    virtual auto Creatable(void) -> void override
    {
        std::cout<<vhc<<" was launched by ";
        Printable();
    }
};


int main()
{
    std::vector<CObj> vec; // How am I supposed to declare this container?

    for(auto i = 0; i < 10; i++)
    {
        CObjTesla obj1;
        CObj obj2;
        vec.push_back(static_cast<CObj>(obj1)); // what should be the correct type of the container?
        vec.push_back((obj2));
    }

    for(auto &iter : vec)
    {
        switch(iter.ObjType())
        {
            case TYPES::TYPE1: 
                iter.Printable();
            break;
            case TYPES::TYPE2: 
                auto temp = const_cast<CObjTesla>(iter); //?? what shoud I do here?
                temp.Creatable();
            break;
            case TYPES::INVALID:
            default:
            break;
        }
    }
}

推荐答案

您可以将不同的对象类型存储在 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!

这篇关于如何在现代C ++中将不同类类型的对象存储到一个容器中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆