Polymorphism without the "new"

Say I have something like the following:

class A { virtual void g() = 0 } class B : public A { virtual void g() { ... } } class C : public A { virtual void g() { ... } } ... f(bool x) { if (x) { return B(); } else { return C(); } } bool get_boolean(); int main() { bool b = get_boolean(); ... x = f(b); xg(); } 

Is there a way to do something like this above without calling new , i.e. only on the stack?

+7
source share
9 answers

In function f objects B() or C() are temporary, so you can only return them from f by value.

Perhaps boost :: variant is for you. Then you donโ€™t even need to have a virtual method or get from a common base class.

+4
source

An easy way to avoid dynamic distribution is to use a static distribution , which roughly corresponds to the opposite dynamic distribution. However, this must be done carefully, because even with a non-threading program, you can inadvertently end up in a situation where two or more parts of the code believe that they "own" some statically distributed object. Even worse, such significant global variables (even if they are hidden as single variables or in the code below as local statics) essentially serve as central hubs for spaghetti exchange, where information causing chaos spreads freely between places you could never have imagined, completely out of your control.

So the static distribution scheme has some disadvantages & hellip ;:-)

But let & rsquo; s start there:

 // Using static allocation. #include <iostream> using namespace std; struct A { virtual void g() = 0; }; struct B : A { virtual void g() override { wcout << "A\n"; } }; struct C : A { virtual void g() override { wcout << "B\n"; } }; A& f( bool const x ) { static B theB; static C theC; if( x ) { theB = B(); return theB; } else { theC = C(); return theC; } } bool get_boolean() { return false; } int main() { bool const b = get_boolean(); A& x = f( b ); xg(); } 

To avoid the erroneous rejection of the static allocation scheme, you can provide storage on the stack using C ++ automatic allocation (automatic distribution C ++ is, by definition, a stack, LIFO distribution scheme). But that means transferring storage to a function. Then the function can return a link to the corresponding object:

 // Using automatic storage (the stack) #include <iostream> using namespace std; struct A { virtual void g() = 0; }; struct B : A { virtual void g() override { wcout << "A\n"; } }; struct C : A { virtual void g() override { wcout << "B\n"; } }; A& f( bool const x, B& b, C& c ) { if( x ) { b = B(); return b; } else { c = C(); return c; } } bool get_boolean() { return false; } int main() { bool const b = get_boolean(); B objBStorage; C objCStorage; A& x = f( b, objBStorage, objCStorage ); xg(); } 

But even when we decide to ignore such problems as building with side effects, etc., i.e. when we blithely assume that classes B and C are designed to work well with such a scheme, the above waste repository.If the instances of B and C are large, you can consider using C ++ objects to build objects in a previously existing repository called new location . Due to memory alignment issues, it's a little hard to do right in C ++ 03, but C ++ 11 offers better support:

 #include <iostream> #include <memory> // unique_ptr #include <new> // new #include <type_traits> // aligned_storage using namespace std; typedef unsigned char Byte; struct A { virtual void g() = 0; }; struct B : A { virtual void g() override { wcout << "A\n"; } }; struct C : A { virtual void g() override { wcout << "B\n"; } }; A* f( bool const x, void* storage ) { return (x? static_cast<A*>( ::new( storage ) B() ) : ::new( storage ) C()); } bool get_boolean() { return false; } void destroyA( A* p ) { p->~A(); } int main() { enum{ enoughBytes = (sizeof( B ) > sizeof( C ))? sizeof( B ) : sizeof( C ) }; typedef aligned_storage< enoughBytes >::type StorageForBOrC; bool const b = get_boolean(); StorageForBOrC storage; A* const pX = f( b, &storage ); unique_ptr<A, void(*)(A*)> const cleanup( pX, destroyA ); pX->g(); } 

Now, which of the above options would I choose?

I would choose a strictly limited, but simple and instantaneous static allocation, or I would choose automatic memory allocation, or perhaps & hellip; optimized, but somewhat complex construction of the object in place?

Answer: I would not choose any of them!

Instead of focusing on microefficiency, I would focus on clarity and correctness and, therefore, just took a hit on the performance of dynamic allocation. For correctness, I would use a smart pointer to the result of the function. If this turned out to be really slowing down, I would probably consider using a dedicated allocator of small objects.

In conclusion, no need to worry about the little things! :-)

+6
source

Polymorphism also works on links, but you cannot return a temporary link, therefore:

 A& f(bool x) { static B b; static C c; if (x) { return b; } else { return c; } } 

and

 A& x = f(b); 
0
source

Is there a way to do something similar above without calling a new one, that is, only on the stack?

You can simply use the new location. This allows you to specify the memory location for your object (for example, in the char buffer that you declare on the stack).


Example: http://www.parashift.com/c++-faq-lite/placement-new.html

0
source

I would reorganize it to use the function argument:

 class A { virtual void g() = 0 } class B : public A { virtual void g() { ... } } class C : public A { virtual void g() { ... } } template<typename FunOb> typename std::result_of<FunOb(A&)>::type f(bool x, FunOb fo) { if (x) { B b; return fo(b); } else { C c; return fo(c); } } bool get_boolean(); int main() { bool b = get_boolean(); f(b, [](A& x) { xg(); } ); } 
0
source

Is there anything in this matter? Why not something simple like below

 void CallGFunc(A *obj){ obj->g(); } int main(){ .... B b; C c; CallGFunc(booleanvar? &b:&c) .... } 

Is this what you want?

0
source

You can use references to objects, however you cannot return a reference to an object created on the stack, because the object will be deleted as soon as the call is completed.

Here is one suggestion:

 #include <iostream> class A { public: virtual void g() const = 0; }; class B : public A { public: virtual void g() const {std::cout << "B" << std::endl;}; }; class C : public A { public: virtual void g() const {std::cout << "C" << std::endl;}; }; const A &f(bool b) { if (b) return B(); else return C(); } void doStuff(const A &a) { ag(); } int main(void) { doStuff(B()); //"B" doStuff(C()); //"C" // A &a = f(true); //ERROR! a will be deleted right after call is made, giving you a reference to an invalid object! return 0; } 

Another solution is to return a reference to the static object, just keep in mind that the object will be used by all instances that call this method. It works well, you do not need to modify the object, but otherwise it is not a very good solution.

 #include <iostream> class A { public: virtual void g() const = 0; }; class B : public A { public: virtual void g() const {std::cout << "B" << std::endl;}; }; class C : public A { public: virtual void g() const {std::cout << "C" << std::endl;}; }; const A &f(bool b) { static B ret1; static C ret2; if (b) return ret1; else return ret2; } void doStuff(const A &a) { ag(); } int main(void) { doStuff(f(true)); //"B" doStuff(f(false)); //"C" return 0; } 
0
source

Not really. The problem is that the copy operation is not polymorphic; if your function returns A then only A will be copied. (This is called cutting.) Without copying, you will need an arbitrary lifetime of the object, not one that is based on an area. And about the only other feature that is usually available is dynamic allocation.

If a function is called only once and the object is more or less singleton, you can use static instances, although you probably want to define them in separate areas to avoid the systematic construction of both of them:

 A* f( bool c ) { A* results = NULL; if ( c ) { static B b; results = &b; } else { static C c; results = &c; } return results; } 

And, of course, there is always an idiom with a letter designation for combining copy with polymorphism. But this leads to an even more dynamic distribution in practice, even if they all occur โ€œunder the hoodโ€, so the client code does not see them. (If the object is immutable, an idiom with a letter designation can use a reference count pointer and avoid most of the extra distributions.)

0
source

If you really want to keep everything on the stack, you better write a function that processes interface A and calls it on the desired object:

 void actionsWithA(A& a){ ag(); } void doItWithB(){ B b; actionsWithA(b); } void doItWithC(){ C c; actionsWithA(c); } bool get_boolean(); int main() { bool b = get_boolean(); if(b) { doItWithB(); } else { doItWithC(); } } 
0
source

All Articles