我怎样才能在栈上创建一个多态对象?(How can I create a polymorphic o

2019-06-27 02:27发布

如何分配堆栈上的多态对象? 我试图做类似(试图避免与新堆分配)的东西?:

A* a = NULL;

switch (some_var)
{
case 1:
    a = A();
    break;
case 2:
    a = B(); // B is derived from A
    break;
default:
    a = C(); // C is derived from A
    break;
}

Answer 1:

不能结构的单一函数这样的工作中,由于条件块内部产生自动或临时对象不能有它们的寿命延伸到包含块。

我建议重构的多态行为到一个单独的功能:

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;
}


Answer 2:

免责声明:我绝对不认为这是一个很好的解决方案。 良好的解决方案,其一是重新考虑设计(也许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());

现在你可以使用之类的东西静态游客事实是一个,如果是一直让我觉得这是没有很好的利用OO多态性的事情。

如果不是一个现成的解决方案,你想用手在其他的答案建议使用放置新的,有一些是因为我们失去了一些在这个过程中定期自动对象的属性是需要人照顾的事情:

  • 编译器不再为我们提供了合适的尺寸和定位;
  • 我们不再去析构函数自动呼叫;

在C ++ 11,这些都是很容易修复与aligned_unionunique_ptr分别。

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...

对于编译器不支持这些功能,你可以选择:升压包括aligned_storagealignment_of可用于构建性状aligned_union ; 和unique_ptr可以用某种范围后卫类的来代替。

现在,这是出路,只是这样很明显,不这样做,只是传递一个临时沿到另一个功能,或者完全重温设计。



Answer 3:

如果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;
        ....
    }
}

如果你想避免浪费三个派生的对象的空间,你可以打破你的方法分为两个部分; 是选择您需要的类型,并在它的工作方法的两个部分。 在决定哪种类型你需要,你调用分配该对象,创建一个指向它,并调用方法的下半年完成堆栈分配对象的工作方法。



Answer 4:

我写了一个通用模板来做到这一点。 可以使用完整的代码在这里 (它成为这个问题的范围太复杂)。 StackVariant对象包含最大类型的大小出提供类型中的一个缓冲器,并且最大取向为好。 对象被构造使用“放置新”和操作员在堆栈上 - >()被用于多态访问建议间接。 此外,为了确保如果虚拟detor定义,应该在堆栈上的物体的破坏称它是非常重要的,所以模板detor仅仅是使用SFINAE定义做。

(见下面的使用示例和输出):

//  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!

要注意以下几点:

  1. 我写的,而试图避免在性能关键的功能堆分配和它做的工作 - 50%的速度上涨。
  2. 我写的利用C ++自身的多态机制。 在此之前,我的代码是完全的切换情况下,像这里以前的建议。


Answer 5:

您不能创建一个多态的局部变量

您不能创建一个多态的局部变量,因为一个子类BA可能比更多的属性A ,因此需要更多的地方,所以编译器必须保留足够的空间供中最大的子类A

  1. 如果你有几十个亚类,其中一人有大量的属性,这会浪费很多的空间。
  2. 如果你把在局部变量的子类的实例, A你作为参数接收,你把你的代码在一个动态库,然后将它链接的代码可以宣布一个子比库中的大,所以编译器不会在堆栈上了分配足够的空间。

所以为它分配自己的空间

使用放置新的 ,你可以初始化你通过一些其他方式分配的空间中的对象:

  • alloca ,但看到这太问题现在看来,这是不是最好的选择。
  • 可变长度数组,与来自某些(非)可移植性乐趣,因为它的工作原理下GCC但不是在C ++标准(甚至在C ++ 11)
  • aligned_union<A, B, C>::type ,如由R. Martinho费尔南德斯到评论建议此答案

然而,这些技术可能会使用大量的额外的空间,如果你给出一个参考(指针)不工作的未知在编译时子A比你占了较大的类型。

我提出的解决方案是对每个子类是一种工厂方法,调用的指针给定的子类的栈上分配的实例提供的函数。 我增加了一个额外的void *的参数所提供的函数的签名,所以可以通过它任意数据。

@MooingDuck建议这个实现使用模板和C ++ 11在下面留言。 如果你需要这个代码不能受益于C ++ 11层的功能,或者用于与结构的替代类一些普通的C代码(如果struct B有类型的第一场struct A ,那么它可以被操纵有点像一条“substruct” A ),然后我的版本下面会做的伎俩(但并不类型安全)。

这个版本与新定义的子类的作品,只要它们实现的ugly工厂般的方法,它会使用堆栈的返回地址和恒定量这个中间功能所需的其他信息,加上的一个实例的大小请求的类,但不是最大的子类的大小(除非您选择使用一个)。

#include <iostream>
class A {
    public:
    int fieldA;
    static void* ugly(void* (*f)(A*, void*), void* param) {
        A instance;
        return f(&instance, param);
    }
    // ...
};
class B : public A {
    public:
    int fieldB;
    static void* ugly(void* (*f)(A*, void*), void* param) {
        B instance;
        return f(&instance, param);
    }
    // ...
};
class C : public B {
    public:
    int fieldC;
    static void* ugly(void* (*f)(A*, void*), void* param) {
        C instance;
        return f(&instance, param);
    }
    // ...
};
void* doWork(A* abc, void* param) {
    abc->fieldA = (int)param;
    if ((int)param == 4) {
        ((C*)abc)->fieldC++;
    }
    return (void*)abc->fieldA;
}
void* otherWork(A* abc, void* param) {
    // Do something with abc
    return (void*)(((int)param)/2);
}
int main() {
    std::cout << (int)A::ugly(doWork, (void*)3);
    std::cout << (int)B::ugly(doWork, (void*)1);
    std::cout << (int)C::ugly(doWork, (void*)4);
    std::cout << (int)A::ugly(otherWork, (void*)2);
    std::cout << (int)C::ugly(otherWork, (void*)11);
    std::cout << (int)B::ugly(otherWork, (void*)19);
    std::cout << std::endl;
    return 0;
}

到那时,我想大家可能都胜过一个简单的成本malloc ,所以你可能会魔杖使用该毕竟。



Answer 6:

你可以放置新的做。 这将放置在堆栈上的项目,包含在缓冲存储器中。 然而,这些变量不是自动的。 缺点是,你的析构函数不会自动运行,您需要妥善销毁它们,就像你已经创造了他们,当他们走出去的范围。

一个合理的选择手动调用析构函数来包装你的类型在一个智能指针,如下所示:

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
}


Answer 7:

多态性不与价值观工作,需要引用或指针。 您可以使用一个常量引用临时对象多态的,它拥有一个栈对象的生命周期。

const A& = (use_b ? B() : A());

如果需要修改的对象,你别无选择,只能动态地分配它(除非你正在使用微软的非标准扩展,可以让你绑定一个临时对象到非const引用)。



Answer 8:

一个的组合char数组和安置new会工作。

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


Answer 9:

要严格回答你的问题-你现在有什么少了点说-即a = A(); 并且a = B()a = C()但这些对象被切片。

为了实现与你的代码多态行为,我,恐怕是不可能的。 编译器需要知道对象的大小提前。 除非你有引用或指针。

如果你使用指针 ,你需要确保它不会结束晃来晃去:

A* a = NULL;

switch (some_var)
{
case 1:
    A obj;
    a = &obj;
    break;
}

不会起作用,因为obj超出范围。 所以,你留下要:

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;
}

这当然是一种浪费。

对于引用这是一个有点棘手,因为他们必须在创建分配,而不能使用的临时变量(除非它是一个const引用)。 所以,你可能需要一个工厂,返回一个持久的参考。



Answer 10:

尽量避免与新堆分配)?

那么在这种情况下,你创建堆栈照常对象和分配地址的基指针 。 但要记住,如果这是一个函数内部完成,没有通过地址作为返回值,因为栈将函数调用返回后放松。

因此,这是不好的。

A* SomeMethod()
{
    B b;
    A* a = &b; // B inherits from A
    return a;
}


Answer 11:

可能的,但它是一个很大的努力,做干净(无需手动放置新暴露的原始缓冲区,这是)。

您现在看到的是这样Boost.Variant ,修改,以限制类型的基类和一些派生类, 揭露多态参考基本类型。

这个东西(PolymorphicVariant?)将包装所有安置新的东西给你(和照顾的安全破坏)。

如果它真的你想要的东西,让我知道,我给你一个开始。 除非你真的需要准确 ,虽然这种行为,迈克·西摩的建议是比较实用的。



Answer 12:

运行此短节目,你就会明白为什么态对象并不在栈上工作得很好。 当你创建一个派生类型是未知的堆栈对象,并期望它从一个函数调用返回,会发生什么情况是当调用函数超出范围的对象被销毁。 因此,对象只只要该函数是范围内的住。 为了返回一个有效的对象,将活得比调用函数需要使用堆。 这证明这个简单的层次结构,并用switch语句,除了一个不会堆栈,另一种则是在堆上相同功能的两个版本。 看看从两种实现输出,并期待看到被称为什么方法,什么课,他们正在从调用,当他们被调用。

#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. 

我并不是说,它不能做; 我只是陈述警告在试图这样做。 它可不明智的尝试做类似的东西。 我不知道有什么办法做到这一点,除非你有一个包装类将包含在堆栈中分配对象来管理他们的生活时间。 我要尝试,并在有效的,看看我能拿出类似的东西。



文章来源: How can I create a polymorphic object on the stack?