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

kis*_*dbn 6 c++

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

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

当我不想将对象存储为指针时,是否允许这样做?如果我必须使用指针,那么推荐或更简洁的方法应该是什么?

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

#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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Kla*_*aus 7

您可以将不同的对象类型存储在std::variant. 如果你这样做,就没有必要有一个通用的接口和使用虚函数。

例子:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是使用这种解决方案有缺点。访问 viastd::visit 可能很慢。有时,例如 gcc 在这种情况下会生成非常糟糕的代码。(跳转表是在运行时生成的,不知道为什么!)。您总是通过表访问调用该函数,这需要一些额外的时间。并且存储对象std::variant总是消耗变体中最大类的大小,此外,您需要一些空间用于变体中的标签变量。

“旧”方法是将原始或更好的智能指针存储到向量中,并通过基指针简单地调用公共接口函数。这里的缺点是每个实例中都有额外的 vtable 指针(通常与 std::variant 中的标签变量大小相同)。使用 vtable 访问调用该函数的间接方式也带来了(小)成本。

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果std::unique_ptr对你来说足够了,你可以使用那个。这取决于在您的设计中是否需要传递指针。

提示:如果你使用指向基类类型的指针,永远不要忘记让你的析构函数成为虚拟的!另请参阅:何时使用虚拟析构函数

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

顺便提一句:

virtual auto ObjType(void) -> TYPES
Run Code Online (Sandbox Code Playgroud)

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