是否可以在any std c++ 容器中维护派生类的知识,而不使用指针,从容器动态转换返回值?我知道我可以创建一个向量,例如某些基类类型的指针,并让它们保留其子类。但问题是我必须使用指针吗?
示例:
struct A {
int x = 0, y = 0, z = 0;
virtual void foo() { cout << "A" << endl; };
};
struct B : public A {
int a = 1, b = 1, c = 1;
virtual void foo() { cout << "B" << endl; };
};
int main() {
<SOMECONTAINER><A> a(2);
a[0] = A();
a[1] = B();
B * p;
B& n = dynamic_cast<B&>(a[1]); // Always throws?
p = dynamic_cast<B*>(&a[1]); // Always zero?
cout << p << endl;
}
是的,你确实必须使用指针。否则,尝试将
B
放入 A
的容器中会导致 slicing:B
被切成 A
(这不仅限于容器,如果您这样做,也会发生完全相同的事情) A a = B()
或者如果您将 B
传递给需要 A
的函数。
当你稍后把它拿出来时,它是一个完全不知道它的血统包括一个
A
类型的杰出祖先的B
——无论你以什么方式看待A
,你都无法使这是一个B
。
我将忽略对齐,或者更确切地说假设指针后面的数据已充分对齐。
template<class T, unsigned N>
struct poly_anna;
template<class T,unsigned N>
struct poly_bob {
typedef poly_anna<T,N> poly_anna_;
T*(*get)(poly_anna_*) = nullptr;
void(*destroy)(poly_anna_*) = nullptr;
void(*move_to)(poly_anna_ *,poly_anna_*) = nullptr;
void(*copy_to)(poly_anna_ const*, poly_anna_*)=nullptr;
};
template<class T, unsigned N>
struct poly_anna {
private:
poly_bob<T,N> const*bob=nullptr;
char buff[N];
public:
template<class U> static poly_bob<T,N> const* get_bob() {
static poly_bob<T,N> b={
[](poly_anna*a)->T&{ return *(U*)&a->buff[0]; },
[](poly_anna*a){ ((U*)&a->buff[0])->~U(); a->bob = nullptr; },
[](poly_anna*s,poly_anna*d){
if (s->bob==d->bob){
*((U*)&d->buff[0])=std::move(*((U*)&d->buff[0]));
return;
}
if (d->bob != nullptr) {
d->bob->destroy(b);
}
d->store( std::move( *(U*)&s->buff[0] ) );
},
[](poly_anna const* s, poly_anna*d){
if (d->bob == s->bob){
*(U*)&d->buff[0] = *(U const*)&s->buff[0];
return;
}
if (d->bob){ d->bob->destroy(d); }
d->store( *(U const*)*s->buff[0] );
}
};
return &b;
};
template<class U_>
void store(U_&& u){
typedef typename std::decay<U_>::type U;
static_assert( sizeof(U)<=N, "N not large enough" );
if (bob) bob->destroy( this );
bob = get_bob<U>();
new (&buff[0]) U( std::forward<U_>(u) );
}
void reset(){ if (bob) bob->destroy(this); }
T& get() {
return bob->get(this);
}
T const& get() const {
return bob->get(const_cast<poly_anna*>(this));
}
poly_anna( poly_anna const& o ){
if (o.bob) o.bob->copy_to( &o, this );
}
poly_anna( poly_anna && o ){
if (o.bob) o.bob->move_to( &o, this );
}
poly_anna&operator=( poly_anna const& o ){
if (o.bob) o.bob->copy_to( &o, this );
else if (bob) bob->destroy(this);
return *this
}
poly_anna&operator=( poly_anna && o ){
if (o.bob) o.bob->move_to( &o, this );
else if (bob) bob->destroy(this);
return *this
}
poly_anna()=default;
~poly_anna(){if(bob)bob->destroy(this);}
explicit operator bool()const{return bob;}
};
这是我对多态变体的尝试。它存储
T
和 T
的子级,只要它们不大于 N
并且可以存储在 std
容器中。
如果编译通过请告诉我。
您需要用于虚拟成员函数调度的指针。
当你想一想,如果没有指针,你就只剩下“按值”了。值语义和多态性在一起并没有真正的意义。
A value 具有单一上下文/类型。它是原子的且简单的。可以这么说,所见即所得。当然,你可以投射它,但这样你就有了......另一个值。
有一句经常被引用的程序员谚语:没有什么问题是通过额外的间接层不能解决的,除了太多的间接层。
boost::variant
。boost::variant
允许您想要存储的所有(子)类,则可以避免使用指针。
这可以是胜利,但不一定是胜利。
在采取这样的解决方案之前先进行衡量。
我认为这里实际上有两个问题:
1 的答案是肯定的,但需要付出一些努力。正如一些人提到的,一种方法是使用像 boost::variant 这样的变体类型。这种方法的问题在于,您失去了与变体中存储的对象自然交互的能力,而必须编写访问者,这会产生大量语法开销。
如果类层次结构是有界的,那么更好的方法可能是使用 boost::variant 的变体(无双关语),专门设计用于保留多态语义及其相关语法。一个例子可能是emplacer。正如上面 dyp 的评论中所指出的,emplacer 是一个受限制的变体。
对于问题 2,我不知道有什么方法可以在不使用 typeid() 或手动类型系统的情况下做到这一点。
我们正在努力引入一个类模板
polymorphic
,它可以让您使用类似值的对象编写多态代码。
SOME_CONTAINER<polymorphic<Shape>> shapes;
shapes.emplace_back(Circle(/*radius=*/5.0));
shapes.emplace_back(Square(/*side=*/3.0));
shapes.emplace_back(Hexagon(/*side=*/2.0));
for (const auto& shape : shapes) {
// Work with a local copy, does not slice.
polymorphic<Shape> local_copy = shape;
// Do something with the local_copy.
// `local_shape` goes out of scope and is correctly deleted.
}
// `shapes` go out of scope and are correctly deleted.
在内部,
polymorphic
使用指针和类型擦除,以便复制和删除适用于派生类型。
这与我们在没有指针的情况下实现多态性一样接近——它们被使用,但在实现中被隐藏。
我们正在进行 C++ 标准提案:
https://wg21.link/p3109 https://wg21.link/p3152
以及参考实现: