gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 13:19:32 25 4
gpt4 key购买 nike

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

在现代 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;
}
}
}

最佳答案

您可以在 std::variant 中存储不同的对象类型.如果这样做,则无需拥有通用接口(interface)并使用虚拟功能。

例子:

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总是消耗变量中最大类的大小,此外,变量内部的标记变量需要一些空间。

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

带有基本类型和 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对你来说足够了,你可以使用那个。这取决于在您的设计中是否需要传递指针。

提示:如果您使用指向基类类型的指针,请不要忘记将析构函数设为虚拟!另见: When to use virtual destructors

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

顺便提一句:
virtual auto ObjType(void) -> TYPES

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

关于c++ - 如何在现代 C++ 中将不同类类型的对象存储到一个容器中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59316961/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com