Polymorphic objects on the stack?

后端 未结 7 669
忘了有多久
忘了有多久 2021-02-01 02:53

In Why is there no base class in C++?, I quoted Stroustrup on why a common Object class for all classes is problematic in c++. In that quote there is the statement:

相关标签:
7条回答
  • 2021-02-01 03:23

    I think the point is that this is not "really" polymorphic (whatever that means :-).

    You could write your test function like this

    template<class T>
    void test(T& obj)
    {
        obj.f();
    }
    

    and it would still work, whether the classes have virtual functions or not.

    0 讨论(0)
  • 2021-02-01 03:30

    Polymorphism without heap allocation is not only possible but also relevant and useful in some real life cases.

    This is quite an old question with already many good answers. Most answers indicate, correctly of course, that Polymorphism can be achieved without heap allocation. Some answers try to explain that in most relevant usages Polymorphism needs heap allocation. However, an example of a viable usage of Polymorphism without heap allocation seems to be required (i.e. not just purely syntax examples showing it to be merely possible).


    Here is a simple Strategy-Pattern example using Polymorphism without heap allocation:

    Strategies Hierarchy

    class StrategyBase {
    public:
        virtual ~StrategyBase() {}
        virtual void doSomething() const = 0;
    };
    
    class Strategy1 : public StrategyBase {
    public:
        void doSomething() const override { std::cout << "Strategy1" << std::endl; }
    };
    
    class Strategy2 : public StrategyBase {
    public:
        void doSomething() const override { std::cout << "Strategy2" << std::endl; }
    };
    

    A non-polymorphic type, holding inner polymorphic strategy

    class A {
        const StrategyBase* strategy;
    public:
        // just for the example, could be implemented in other ways
        const static Strategy1 Strategy_1;
        const static Strategy2 Strategy_2;
    
        A(const StrategyBase& s): strategy(&s) {}
        void doSomething() const { strategy->doSomething(); }
    };
    
    const Strategy1 A::Strategy_1 {};
    const Strategy2 A::Strategy_2 {};
    

    Usage Example

    int main() {    
      // vector of non-polymorphic types, holding inner polymorphic strategy
      std::vector<A> vec { A::Strategy_1, A::Strategy_2 };
    
      // may also add strategy created on stack 
      // using unnamed struct just for the example
      struct : StrategyBase {
        void doSomething() const override {
          std::cout << "Strategy3" << std::endl;
        }
      } strategy3;
    
      vec.push_back(strategy3);
    
      for(auto a: vec) {
        a.doSomething();
      }
    }
    

    Output:

    Strategy1
    Strategy2
    Strategy3
    

    Code: http://coliru.stacked-crooked.com/a/21527e4a27d316b0

    0 讨论(0)
  • 2021-02-01 03:34

    Having read it I think the point is (especially given the second sentence about copy-semantics) that universal base class is useless for objects handled by value, so it would naturally lead to more handling via reference and thus more memory allocation overhead (think template vector vs. vector of pointers).

    So I think he meant that the objects would have to be allocated separately from any structure containing them and that it would have lead to many more allocations on heap. As written, the statement is indeed false.

    PS (ad Captain Giraffe's comment): It would indeed be useless to have function

    f(object o)
    

    which means that generic function would have to be

    f(object &o)
    

    And that would mean the object would have to be polymorphic which in turn means it would have to be allocated separately, which would often mean on heap, though it can be on stack. On the other hand now you have:

    template <typename T>
    f(T o) // see, no reference
    

    which ends up being more efficient for most cases. This is especially the case of collections, where if all you had was a vector of such base objects (as Java does), you'd have to allocate all the objects separately. Which would be big overhead especially given the poor allocator performance at time C++ was created (Java still has advantage in this because copying garbage collector are more efficient and C++ can't use one).

    0 讨论(0)
  • 2021-02-01 03:37

    I think he was going along the lines of not being able to store it in a base-typed variable. You're right in saying that you can store it on the stack if it's of the derived type because there's nothing special about that; conceptually, it's just storing the data of the class and it's derivatives + a vtable.

    edit: Okay, now I'm confused, re-looking at the example. It looks like you may be right now...

    0 讨论(0)
  • 2021-02-01 03:44

    I think Bjarne means that obj, or more precisely the object it points to, can't easily be stack-based in this code:

    int f(int arg) 
    { 
        std::unique_ptr<Base> obj;    
        switch (arg) 
        { 
        case 1:  obj = std::make_unique<Derived1      >(); break; 
        case 2:  obj = std::make_unique<Derived2      >(); break; 
        default: obj = std::make_unique<DerivedDefault>(); break; 
        } 
        return obj->GetValue(); 
    }
    

    You can't have an object on the stack which changes its class, or is initially unsure what exact class it belongs to.

    (Of course, to be really pedantic, one could allocate the object on the stack by using placement-new on an alloca-allocated space. The fact that there are complicated workarounds is beside the point here, though.)

    The following code also doesn't work as might be expected:

    int f(int arg) 
    { 
        Base obj = DerivedFactory(arg); // copy (return by value)
        return obj.GetValue();
    }
    

    This code contains an object slicing error: The stack space for obj is only as large as an instance of class Base; when DerivedFactory returns an object of a derived class which has some additional members, they will not be copied into obj which renders obj invalid and unusable as a derived object (and quite possibly even unusable as a base object.)

    Summing up, there is a class of polymorphic behaviour that cannot be achieved with stack objects in any straightforward way.


    Of course any completely constructed derived object, wherever it is stored, can act as a base object, and therefore act polymorphically. This simply follows from the is-a relationship that objects of inherited classes have with their base class.

    0 讨论(0)
  • 2021-02-01 03:46

    Bjarne's statement is not correct.

    Objects, that is instances of a class, become potentially polymorphic by adding at least one virtual method to their class declaration. Virtual methods add one level of indirection, allowing a call to be redirected to the actual implementation which might not be known to the caller.

    For this it does not matter whether the instance is heap- or stack-allocated, as long as it is accessed through a reference or pointer (T& instance or T* instance).

    One possible reason why this general assertion slipped onto Bjarne's web page might be that it is nonetheless extremely common to heap-allocate instances with polymorphic behavior. This is mainly because the actual implementation is indeed not known to the caller who obtained it through a factory function of some sort.

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