将std :: vector存储在主机父班中的最佳方法
我想在主机类中存储一个std :: vector<>
包含具有共同基类的对象。主机类应保持共配,因为它存储在其所有者类的std :: vector<>
中。
C ++提供了多种方法,但我想知道最佳实践。
这是一个示例,使用std :: shared_ptr>>
:
class Base{};
class Derivative1: public Base{};
class Derivative2: public Base{};
class Host{
public: std::vector<std::shared_ptr<Base>> _derivativeList_{};
};
class Owner{
public: std::vector<Host> _hostList_;
};
int main(int argc, char** argv){
Owner o;
o._hostList_.resize(10);
Host& h = o._hostList_[0];
h._derivativeList_.emplace_back(std::make_shared<Derivative1>());
// h._derivativeList_.resize(10, std::make_shared<Derivative1>()); // all elements share the same pointer, but I don't want that.
}
这里的主要缺点是为了在_derivativelist _ 我需要为每个元素执行
我无法与emplace_back()
。这比简单的ressize(n)
要花费更多的时间,该std :: shared_ptr&gt;&gt;
一起使用,因为它将为其创建相同的指针实例每个插槽。
我考虑过使用std :: simolor_ptr&lt;&gt;
,但这是不可行的,因为它使host
class class class nonable(> std请求的功能: :vector
)。
否则,我可以使用std :: variant&lt; dedield1,derived2&gt;
可以做我想要的。但是,我需要声明派生类的每一个可能的实例...
对此有任何想法/建议吗?
I want to store a std::vector<>
containing objects which have a common base class, within a host class. The host class should remain copiable since it is stored inside a std::vector<>
of it's owner class.
C++ offers multiple ways of doing that, but I want to know the best practice.
Here is an example using std::shared_ptr<>
:
class Base{};
class Derivative1: public Base{};
class Derivative2: public Base{};
class Host{
public: std::vector<std::shared_ptr<Base>> _derivativeList_{};
};
class Owner{
public: std::vector<Host> _hostList_;
};
int main(int argc, char** argv){
Owner o;
o._hostList_.resize(10);
Host& h = o._hostList_[0];
h._derivativeList_.emplace_back(std::make_shared<Derivative1>());
// h._derivativeList_.resize(10, std::make_shared<Derivative1>()); // all elements share the same pointer, but I don't want that.
}
Here the main drawback for me is that in order to claim a lot of elements in _derivativeList_
I need to perform emplace_back()
for every single element. This takes a lot more time than a simple resize(N)
which I can't use with std::shared_ptr<>
since it will create the same pointer instance for every slot.
I thought about using std::unique_ptr<>
instead, but this is not viable since it makes the Host
class non copiable (a feature requested by std::vector
).
Otherwise, I could use std::variant<Derived1, Derived2>
which can do what I want. However I would need to declare every possible instance of the derived class...
Any thought/advice about this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
TLDR:根据上下文使用变体或类型擦除。
您在C ++中要求的内容将被描述为 是一个值类型或具有价值语义的类型。您需要一种可复制的类型,并仅复制“做正确的事”(副本不共享所有权)。但是与此同时,您想要多态性。您想拥有各种满足相同界面的类型。因此...多态值类型。
价值类型更易于使用,因此它们将使界面更加愉快。但是,它们实际上可能会更糟,并且实施更为复杂。因此,与一切一样,酌处权和判断力发挥作用。但是我们仍然可以谈论实施它们的“最佳实践”。
让我们添加一个接口方法,以便我们可以说明以下一些相对优点:
有两种常见的方法:变体和类型擦除。这些是我们在C ++中的最佳选择。
正如您所说,变体
是当类型集有限且关闭时的最佳选择。预计其他开发人员不会以自己的类型添加到集合中。
直接使用该变体的缺点是:
BASELIKE
的作用不像base
。您可以复制它,但不能实现接口。任何使用它都需要访问。因此,您可以用一个小包装器将其包装:
现在您有了一个列表,您可以同时将
derivativation1
和derivative2
和对待元素的引用,就像任何<代码>基础&amp; 。有趣的是,
base
没有提供太多价值。借助抽象方法,您知道所有派生的类都正确实现了它。但是,在这种情况下,我们知道所有派生的类,如果它们无法实现该方法,则访问将无法编译。因此,基本
实际上没有提供任何值。如果我们需要谈论界面,我们可以通过定义一个概念来做到这一点:
最后,此选项看起来像: https://godbolt.org/z/7yw9fpv6y
类型擦除
假设我们有一组开放的类型。
经典和最简单的方法是在指针中流量或引用通用基类。如果您还想要所有权,请将其放入
unique_ptr
中。 (shared_ptr
不太合适。)然后,您必须实现复制操作,因此将unique_ptr
放入包装器类型中并定义复制操作。经典方法是将方法定义为基类接口的一部分clone()
,每个派生的类都覆盖以复制自身。unique_ptr
包装器可以在需要复制时调用该方法。这是一种有效的方法,尽管它具有一些权衡。需要基础类是侵入性的,如果您同时想满足多个接口,可能会很痛苦。
std :: vector&lt; t&gt;
andstd :: set&lt; t&gt;
不共享普通基类,但两者都可以。另外,clone()
方法是纯样板。类型擦除将这一步骤更进一步,并消除了对普通基类的需求。
在这种方法中,您仍然定义一个基类,但是对于您而不是用户:
并且定义了作为特定类型特定委托的实现。同样,这是给您的,而不是您的用户:
然后您可以定义用户与用户交互的类型类型:
最后,此选项看起来像: https://godbolt.org/z/p3zt9nb5o
tldr: Use a variant or type erasure, depending on context.
What you are asking for in C++ would be described roughly as a value type or a type with value semantics. You want a type that is copyable, and copying just "does the right thing" (copies do not share ownership). But at the same time you want polymorphism. You want to hold a variety of types that satisfy the same interface. So... a polymorphic value type.
Value types are easier to work with, so they will make a more pleasant interface. But, they may actually perform worse, and they are more complex to implement. Therefore, as with everything, discretion and judgment come into play. But we can still talk about the "best practice" for implementing them.
Let's add an interface method so we can illustrate some of the relative merits below:
There are two common approaches: variants and type erasure. These are the best options we have in C++.
Variants
As you imply, variants are the best option when the set of types is finite and closed. Other developers are not expected to add to the set with their own types.
There's a downside to using the variant directly:
BaseLike
doesn't act like aBase
. You can copy it, but it doesn't implement the interface. Any use of it requires visitation.So you would wrap it with a small wrapper:
Now you have a list in which you can put both
Derivative1
andDerivative2
and treat a reference to an element as you would anyBase&
.What's interesting now is that
Base
is not providing much value. By virtue of the abstract method, you know that all derived classes correctly implement it. However, in this scenario, we know all the derived classes, and if they fail to implement the method, the visitation will fail to compile. So,Base
is actually not providing any value.If we need to talk about the interface we can do so by defining a concept:
In the end, this option looks like: https://godbolt.org/z/7YW9fPv6Y
Type Erasure
Suppose instead we have an open set of types.
The classical and simplest approach is to traffic in pointers or references to a common base class. If you also want ownership, put it in a
unique_ptr
. (shared_ptr
is not a good fit.) Then, you have to implement copy operations, so put theunique_ptr
inside a wrapper type and define copy operations. The classical approach is to define a method as part of the base class interfaceclone()
which every derived class overrides to copy itself. Theunique_ptr
wrapper can call that method when it needs to copy.That's a valid approach, although it has some tradeoffs. Requiring a base class is intrusive, and may be painful if you simultaneously want to satisfy multiple interfaces.
std::vector<T>
andstd::set<T>
do not share a common base class but both are iterable. Additionally, theclone()
method is pure boilerplate.Type erasure takes this one step more and removes the need for a common base class.
In this approach, you still define a base class, but for you, not your user:
And you define an implementation that acts as a type-specific delegator. Again, this is for you, not your user:
And then you can define the type-erased type that the user interacts with:
In the end, this option looks like: https://godbolt.org/z/P3zT9nb5o