C++ 如何在堆栈上创建多态对象?
如何在堆栈上分配多态对象?我正在尝试做类似的事情(尝试使用new避免堆分配): 无法创建多态局部变量 您不能创建多态局部变量,因为C++ 如何在堆栈上创建多态对象?,c++,stack,polymorphism,C++,Stack,Polymorphism,如何在堆栈上分配多态对象?我正在尝试做类似的事情(尝试使用new避免堆分配): 无法创建多态局部变量 您不能创建多态局部变量,因为a的子类B的属性可能比a多,因此发生的次数更多,因此编译器必须为a的最大子类保留足够的空间 如果您有几十个子类,其中一个子类有大量属性,这将浪费大量空间 如果您将作为参数接收的a的子类的一个实例放入局部变量,并将代码放入动态库中,那么与它链接的代码可能会声明一个比库中的子类大的子类,因此编译器无论如何都不会在堆栈上分配足够的空间 所以自己分配空间 使用,可以通过其他方
a
的子类B
的属性可能比a
多,因此发生的次数更多,因此编译器必须为a
的最大子类保留足够的空间
a
的子类的一个实例放入局部变量,并将代码放入动态库中,那么与它链接的代码可能会声明一个比库中的子类大的子类,因此编译器无论如何都不会在堆栈上分配足够的空间
,但看到它似乎不是最好的选择alloca
- 一个可变长度的数组,它带来了一些(非)可移植性的乐趣,因为它在GCC下工作,但是
- 对齐的联合体
a
的未知子类提供了一个大于所考虑类型的引用(指针),那么这些技术就不起作用
我建议的解决方案是在每个子类上都有一种工厂方法,它使用指向给定子类的堆栈分配实例的指针调用提供的函数。我在提供的函数的签名中添加了一个额外的void*参数,因此可以传递任意数据
@MooingDuck在下面的评论中建议使用模板和C++11。如果无法从C++11特性中获益的代码,或者某些使用结构而不是类的普通C代码(如果struct B
的第一个字段的类型为struct a
,那么它可以像a
的“子结构”一样进行操作),那么下面的我的版本就可以做到这一点(但不是类型安全的)
这个版本适用于新定义的子类,只要它们实现了类似工厂的丑陋的方法,并且它将使用固定数量的堆栈作为这个中间函数所需的返回地址和其他信息,加上所请求类的实例的大小,而不是最大子类的大小(除非您选择使用该选项)
#包括
甲级{
公众:
国际田联;
静态无效*丑陋(无效*(*f)(A*,无效*),无效*参数){
实例;
返回f(&实例,参数);
}
// ...
};
B类:公共A{
公众:
int fieldB;
静态无效*丑陋(无效*(*f)(A*,无效*),无效*参数){
B实例;
返回f(&实例,参数);
}
// ...
};
丙类:公共乙类{
公众:
int fieldC;
静态无效*丑陋(无效*(*f)(A*,无效*),无效*参数){
C实例;
返回f(&实例,参数);
}
// ...
};
void*doWork(A*abc,void*param){
abc->fieldA=(int)参数;
如果((int)参数==4){
((C*)abc)->fieldC++;
}
返回(作废*)abc->fieldA;
}
作废*其他工作(A*abc,作废*参数){
//与abc合作
返回(void*)((int)参数)/2;
}
int main(){
std::cout如果B是基本类型D1、D2和D3是派生类型:
void foo()
{
D1 derived_object1;
D2 derived_object2;
D3 derived_object3;
B *base_pointer;
switch (some_var)
{
case 1: base_pointer = &derived_object1; break;
....
}
}
如果您想避免浪费三个派生对象的空间,可以将方法分为两部分:选择所需类型的部分和在其上工作的方法部分。在确定所需类型后,您可以调用一个分配该对象的方法,创建指向该对象的指针,并调用m的后半部分方法完成堆栈分配对象上的工作。多态性不适用于值,您需要引用或指针。您可以多态性地使用临时对象的常量引用,它将具有堆栈对象的生存期
const A& = (use_b ? B() : A());
如果您需要修改对象,您别无选择,只能动态分配它(除非您使用的是Microsoft的非标准扩展,允许您将临时对象绑定到非常量引用)。免责声明:我绝对不认为这是一个好的解决方案。好的解决方案是重新考虑设计(鉴于存在有限数量的可能性,这里可能不保证OO多态性?),或者使用第二个函数通过引用传递所述多态对象
但由于其他人提到了这个想法,但细节搞错了,所以我发布这个答案是为了展示如何把它做好。希望我能把它做好
很明显,可能的类型的数量是有界的。这意味着一个有区别的联合,如boost::variant
可以解决这个问题,即使它不漂亮:
boost::variant<A, B, C> thingy =
some_var == 1? static_cast<A&&>(A())
: some_var == 2? static_cast<A&&>(B())
: static_cast<A&&>(C());
对于不支持这些功能的编译器,您可以选择:Boost包括aligned\u存储
和alignment\u特性,这些特性可用于构建aligned\u联合
;以及unique\u ptr
可替换为某种范围保护类
现在,这已经不是问题了,很明显,不要这样做,只需将一个临时变量传递给另一个函数,或者完全重新访问设计。您可以使用placement new来完成。这将把项目放在堆栈上,放在缓冲区中包含的内存中。但是,这些变量不是自动的。缺点是您需要r析构函数不会自动运行,当它们超出范围时,您需要像创建它们一样正确地析构函数
手动调用析构函数的合理替代方法是将您的类型包装在智能指针中,如下所示:
class A
{
public:
virtual ~A() {}
};
class B : public A {};
class C : public B {};
template<class T>
class JustDestruct
{
public:
void operator()(const T* a)
{
a->T::~T();
}
};
void create(int x)
{
char buff[1024] // ensure that this is large enough to hold your "biggest" object
std::unique_ptr<A, JustDestruct<T>> t(buff);
switch(x)
{
case 0:
ptr = new (buff) A();
break;
case 1:
ptr = new (buff) B();
break;
case 2:
ptr = new (buff) C();
break;
}
// do polymorphic stuff
}
A类
{
公众:
虚拟~A(){}
};
B类:p
boost::variant<A, B, C> thingy =
some_var == 1? static_cast<A&&>(A())
: some_var == 2? static_cast<A&&>(B())
: static_cast<A&&>(C());
std::aligned_union<A, B, C>::type thingy;
A* ptr;
switch (some_var)
{
case 1:
ptr = ::new(&thingy.a) A();
break;
case 2:
ptr = ::new(&thingy.b) B();
break;
default:
ptr = ::new(&thingy.c) C();
break;
}
std::unique_ptr<A, void(*)(A*)> guard { ptr, [](A* a) { a->~A(); } };
// all this mechanism is a great candidate for encapsulation in a class of its own
// but boost::variant already exists, so...
class A
{
public:
virtual ~A() {}
};
class B : public A {};
class C : public B {};
template<class T>
class JustDestruct
{
public:
void operator()(const T* a)
{
a->T::~T();
}
};
void create(int x)
{
char buff[1024] // ensure that this is large enough to hold your "biggest" object
std::unique_ptr<A, JustDestruct<T>> t(buff);
switch(x)
{
case 0:
ptr = new (buff) A();
break;
case 1:
ptr = new (buff) B();
break;
case 2:
ptr = new (buff) C();
break;
}
// do polymorphic stuff
}
char buf[<size big enough to hold largest derived type>];
A *a = NULL;
switch (some_var)
{
case 1:
a = new(buf) A;
break;
case 2:
a = new(buf) B;
break;
default:
a = new(buf) C;
break;
}
// do stuff with a
a->~A(); // must call destructor explicitly
A* a = NULL;
switch (some_var)
{
case 1:
A obj;
a = &obj;
break;
}
A* a = NULL;
A obj1;
B obj2;
C obj3;
switch (some_var)
{
case 1:
a = &obj1;
break;
case 2:
a = &obj2;
break;
case 3:
a = &obj3;
break;
}
A* SomeMethod()
{
B b;
A* a = &b; // B inherits from A
return a;
}
void do_something(A&&);
switch (some_var)
{
case 1:
do_something(A());
break;
case 2:
do_something(B()); // B is derived from A
break;
default:
do_something(C()); // C is derived from A
break;
}
// compile: g++ file.cpp -std=c++11
#include <type_traits>
#include <cstddef>
// union_size()/union_align() implementation in gist link above
template<class Tbaseclass, typename...classes>
class StackVariant {
alignas(union_align<classes...>()) char storage[union_size<classes...>()];
public:
inline Tbaseclass* operator->() { return ((Tbaseclass*)storage); }
template<class C, typename...TCtor_params>
StackVariant& init(TCtor_params&&...fargs)
{
new (storage) C(std::forward<TCtor_params>(fargs)...); // "placement new"
return *this;
};
template<class X=Tbaseclass>
typename std::enable_if<std::has_virtual_destructor<X>::value, void>::type
call_dtor(){
((X*)storage)->~X();
}
template<class X=Tbaseclass>
typename std::enable_if<!std::has_virtual_destructor<X>::value, void>::type
call_dtor() {};
~StackVariant() {
call_dtor();
}
};
#include <cstring>
#include <iostream>
#include "StackVariant.h"
class Animal{
public:
virtual void makeSound() = 0;
virtual std::string name() = 0;
virtual ~Animal() = default;
};
class Dog : public Animal{
public:
void makeSound() final { std::cout << "woff" << std::endl; };
std::string name() final { return "dog"; };
Dog(){};
~Dog() {std::cout << "woff bye!" << std::endl;}
};
class Cat : public Animal{
std::string catname;
public:
Cat() : catname("gonzo") {};
Cat(const std::string& _name) : catname(_name) {};
void makeSound() final { std::cout << "meow" << std::endl; };
std::string name() final { return catname; };
};
using StackAnimal = StackVariant<Animal, Dog, Cat>;
int main() {
StackAnimal a1;
StackAnimal a2;
a1.init<Cat>("gonzo2");
a2.init<Dog>();
a1->makeSound();
a2->makeSound();
return 0;
}
// Output:
// meow
// woff
// woff bye!
#include <string>
#include <iostream>
class Base {
public:
enum Type {
DERIVED_A = 0,
DERIVED_B,
DERIVED_C
};
protected:
Type type_;
public:
explicit Base(Type type) : type_(type) {
std::cout << "Base Constructor Called." << std::endl;
}
virtual ~Base() {
std::cout << "Base Destructor Called." << std::endl;
}
virtual void doSomething() {
std::cout << "This should be overridden by derived class without making this a purely virtual method." << std::endl;
}
Type getType() const { return type_; }
};
class DerivedA : public Base {
public:
DerivedA() : Base(DERIVED_A) {
std::cout << "DerivedA Constructor Called." << std::endl;
}
virtual ~DerivedA() {
std::cout << "DerivedA Destructor Called." << std::endl;
}
void doSomething() override {
std::cout << "DerivedA overridden this function." << std::endl;
}
};
class DerivedB : public Base {
public:
DerivedB() : Base(DERIVED_B) {
std::cout << "DerivedB Constructor Called." << std::endl;
}
virtual ~DerivedB() {
std::cout << "DerivedB Destructor Called." << std::endl;
}
void doSomething() override {
std::cout << "DerivedB overridden this function." << std::endl;
}
};
class DerivedC : public Base {
public:
DerivedC() : Base(DERIVED_C) {
std::cout << "DerivedC Constructor Called." << std::endl;
}
virtual ~DerivedC() {
std::cout << "DerivedC Destructor Called." << std::endl;
}
void doSomething() override {
std::cout << "DerivedC overridden this function." << std::endl;
}
};
Base* someFuncOnStack(Base::Type type) {
Base* pBase = nullptr;
switch (type) {
case Base::DERIVED_A: {
DerivedA a;
pBase = dynamic_cast<Base*>(&a);
break;
}
case Base::DERIVED_B: {
DerivedB b;
pBase = dynamic_cast<Base*>(&b);
break;
}
case Base::DERIVED_C: {
DerivedC c;
pBase = dynamic_cast<Base*>(&c);
break;
}
default: {
pBase = nullptr;
break;
}
}
return pBase;
}
Base* someFuncOnHeap(Base::Type type) {
Base* pBase = nullptr;
switch (type) {
case Base::DERIVED_A: {
DerivedA* pA = new DerivedA();
pBase = dynamic_cast<Base*>(pA);
break;
}
case Base::DERIVED_B: {
DerivedB* pB = new DerivedB();
pBase = dynamic_cast<Base*>(pB);
break;
}
case Base::DERIVED_C: {
DerivedC* pC = new DerivedC();
pBase = dynamic_cast<Base*>(pC);
break;
}
default: {
pBase = nullptr;
break;
}
}
return pBase;
}
int main() {
// Function With Stack Behavior
std::cout << "Stack Version:\n";
Base* pBase = nullptr;
pBase = someFuncOnStack(Base::DERIVED_B);
// Since the above function went out of scope the classes are on the stack
pBase->doSomething(); // Still Calls Base Class's doSomething
// If you need these classes to outlive the function from which they are in
// you will need to use heap allocation.
// Reset Base*
pBase = nullptr;
// Function With Heap Behavior
std::cout << "\nHeap Version:\n";
pBase = someFuncOnHeap(Base::DERIVED_C);
pBase->doSomething();
// Don't Forget to Delete this pointer
delete pBase;
pBase = nullptr;
char c;
std::cout << "\nPress any key to quit.\n";
std::cin >> c;
return 0;
}
Stack Version:
Base Constructor Called.
DerivedB Constructor Called.
DerivedB Destructor Called.
Base Destructor Called.
This should be overridden by derived class without making this a purely virtual method.
Heap Version:
Base Constructor Called.
DerivedC Constructor Called.
DerivedC overridden this function.
DerivedC Destructor called.
Base Destructor Called.