如何保持在堆栈上分配的对象的动态类型
How to keep the dynamic type of an object allocated on the stack?
假设我有:
class A { };
class B : public A { };
A f() {
if (Sunday())
return A;
else
return B;
}
显然这不起作用,因为A
的复制构造函数将被调用。是否有返回堆栈分配对象而不丢失它的类型?
我试过使用std::shared_ptr<A>
,但它让我进入另一个问题,因为std::shared_ptr<B>
不是std::shared_ptr<A>
。
不可能立即从创建该对象的函数中返回一个堆栈分配的(即本地)对象。局部对象在函数返回时销毁。您可以通过使用各种"智能指针"和类似的技术来隐藏/混淆对象分配的实际性质,但是对象本身应该动态地分配。
除此之外,只要遵守本地对象生命周期规则,本地对象的多态性就会以与任何其他对象完全相同的方式工作。只需使用指针或引用
A a;
B b;
A *p = Sunday() ? &a : &b;
// Here `*p` is a polymorphic object
指针p
在上面的例子中保持有效,只要本地对象存在,这意味着你不能从函数返回p
。
if
语句的不同分支中创建这样的对象的不同版本,原因与你不能从函数多态地返回一个局部对象的原因完全相同:一旦创建对象的局部块完成,对象就被销毁。
后一个问题可以通过使用原始缓冲区和手动就地构造
来解决。alignas(A) alignas(B) char object_buffer[1024];
// Assume it's big enough for A and B
A *p = Sunday() ? new(buffer) A() : new (buffer) B();
// Here `*p` is a polymorphic object
p->~A(); // Virtual destructor is required here
但它看起来不漂亮。类似的技术(涉及复制缓冲区)可能可以用于使局部多态对象在块边界下存活(参见@Dietmar k
所以,再一次,如果你只想创建两个对象中的一个,并且让你的对象在块边界中存活,那么立即解决放置局部对象的问题是不可能的。您必须使用动态分配的对象。
由于切片,这是不可能的。使用std::unique_ptr代替。您不会丢失动态类型,但是只能通过A
接口访问它。
最简单的方法当然是使用合适的智能指针,例如,std::unique_ptr<A>
,作为返回类型,并在堆上分配对象:
std::unique_ptr<A> f() {
return std::unique_ptr<A>(Sunday()? new B: new A);
}
对于返回可能指向B
的std::unique_ptr<A>
的方法,A
必须具有virtual
析构函数,否则当std::unique_ptr<A>
实际指向B
对象时,代码可能导致未定义的行为。如果A
没有virtual
析构函数并且无法更改,则可以通过使用合适的std::shared_ptr<...>
或使用合适的std::unique_ptr<...>
:
std::unique_ptr<A, void(*)(A*)> f() {
if (Sunday()) {
return std::unique_ptr<A, void(*)(A*)>(new B, [](A* ptr){ delete static_cast<B*>(ptr); });
}
else {
return std::unique_ptr<A, void(*)(A*)>(new A, [](A* ptr){ delete ptr; });
}
}
如果你不想在堆上分配对象,你可以使用一个holder类型,它将union
与A
和B
存储在一起,然后适当地构造和析构(下面的代码假设A
或B
的副本不会抛出异常;如有必要,可以添加合适的移动构造和移动赋值):
class holder {
bool is_b;
union {
A a;
B b;
} element;
public:
holder(): is_b(Sunday()) {
if (this->is_b) {
new(&this->element.b) B();
}
else {
new(&this->element.a) A();
}
}
holder(holder const& other) { this->copy(other); }
void copy(holder const& other) {
this->is_b = other.is_b;
if (this->is_b) {
new(&this->element.b) B(other.element.b);
}
else {
new(&this->element.a) A(other.element.a);
}
}
~holder() { this->destroy(); }
void destroy() {
if (this->is_b) {
this->element.b.~B();
}
else {
this->element.a.~A();
}
}
holder& operator= (holder const& other) {
this->destroy();
this->copy(other);
return *this;
}
operator A const&() const { return this->is_b? this->element.b: this->element.a; }
operator A&() { return this->is_b? this->element.b: this->element.a; }
};
- C++中的动态对象与非动态对象
- 对具有动态分配的内存和析构函数的类对象的引用
- 我有一个对象,它将在整个程序的持续时间内实例化,但一个类成员不会,我应该动态分配它吗?
- 在对象指针上调用 Delete 是否会递归删除其动态分配的成员
- 生成文件:动态源文件名和对象目录
- 我们可以通过 IPC 传递具有动态管理成员的类对象吗?
- 使用动态实例化的对象填充矢量的快速方法
- 如何在 Cheerp/js 中迭代动态命名的对象?
- 销毁C++中动态分配的内存(数组对象)
- 创建动态对象并将其推送到矢量中
- 删除包含包含动态对象的 STL 容器的智能指针
- C++ 将抽象类型的动态分配对象传递给函数并存储在向量中
- 如何在运行时在对象数组中动态追加新对象C++并打印它们
- 将基类分配给派生对象,反之亦然,以C++以及静态和动态对象之间的差异
- 如何访问对象动态数组中的私有成员变量?
- 如果您为类的一个对象动态分配内存作为参数,会发生什么
- Cython 中对象动态大小的数组
- malloc分配的对象动态类型是什么
- C++按属性排序的对象动态数组
- visual C++新与Malloc的对象动态内存阵列