How can I create a polymorphic object on the stack?

前端 未结 12 1670
谎友^
谎友^ 2021-01-12 05:40

How do I allocate a polymorphic object on the stack? I\'m trying to do something similar to (trying to avoid heap allocation with new)?:

A* a = NULL;

switch         


        
相关标签:
12条回答
  • 2021-01-12 06:16

    Polymorphism doesn't work with values, you need a reference or a pointer. You can use a const reference to a temporary object polymorphically and it will have the lifetime of a stack object.

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

    If you need to modify the object, you have no choice but to dynamically allocate it (unless you're using Microsoft's non-standard extension that lets you bind a temporary object to a non-const reference).

    0 讨论(0)
  • 2021-01-12 06:16

    A combination of a char array and placement new would work.

    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
    
    0 讨论(0)
  • 2021-01-12 06:19

    Run this short program and you'll see why polymorphic objects do not work on the stack very well. When you create a stack object of a derived type that is unknown and expect it to be returned from a function call, what happens is the object is destroyed when that calling function goes out of scope. Thus the object only lives as long as that function is within scope. In order to return a valid object that will outlive the calling function you need to use the heap. This is demonstrated with this simple hierarchy and two versions of the same function with a switch statement except one does the stack and the other does it on the heap. Look at the output from both implementations and look to see what methods are called, what class they are being called from and when they are being called.

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

    Output:

    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. 
    

    I'm not saying that it can not be done; I'm just stating the caveats in trying to do so. It may be ill-advised to try to do something of the sort. I do not know of any way to do this unless if you have a wrapper class that will contain the stack allocated objects to manage their life time. I'll have to try and work on that to see if I can come up with something of the sort.

    0 讨论(0)
  • 2021-01-12 06:25

    If B is your base types D1, D2, and D3 are your derived types:

    void foo()
    {
        D1  derived_object1;
        D2  derived_object2;
        D3  derived_object3;
        B *base_pointer;
    
        switch (some_var)
        {
            case 1:  base_pointer = &derived_object1;  break;
            ....
        }
    }
    

    If you want to avoid wasting the space of the three derived objects, you could break up your method into two parts; the part that chooses which type you need, and the part of the method that works on it. Having decided which type you need, you call a method that allocates that object, creates a pointer to it, and calls the second half of the method to complete the work on the stack-allocated object.

    0 讨论(0)
  • 2021-01-12 06:26

    You can do it with placement new. This will place the items on the stack, in the memory contained in the buffer. However, these variables are not automatic. The downside is that your destructors won't run automatically, you would need to properly destruct them just as you've created them when they go out of scope.

    A reasonable alternative to manually calling the destructor is to wrap your type in a smart pointer, as shown below:

    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
    }
    
    0 讨论(0)
  • 2021-01-12 06:30

    It is possible, but it's a lot of effort to do cleanly (without manual placement new and exposed raw buffers, that is).

    You're looking at something like Boost.Variant, modified to restrict the types to a base class and some derived classes, and to expose a polymorphic reference to the base type.

    This thing (PolymorphicVariant ?) would wrap all the placement new stuff for you (and also take care of safe destruction).

    If it's really what you want, let me know and I'll give you a start. Unless you really need exactly this behaviour though, Mike Seymour's suggestion is more practical.

    0 讨论(0)
提交回复
热议问题