我遇到了这个问题,我想将不同的类(共享相同的接口)存储到一个公共容器中。
在现代 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)
您可以将不同的对象类型存储在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这里不需要,因为在编写函数参数列表之前返回类型是已知的。在这种情况下,需要推导模板参数来定义返回类型,这是需要的,但这里不需要!请不要使用总是自动!
| 归档时间: |
|
| 查看次数: |
641 次 |
| 最近记录: |