This is the original GotW problem and solution substantially as posted to Usenet. See the book More Exceptional C++ (Addison-Wesley, 2002) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard. |
Nested Functions Difficulty: 4 / 10C++ has nested classes, but not nested functions. When might nested functions be useful, and can they be simulated in C++? ProblemJG Questions1. What is a nested class? Why can it be useful? 2. What is a local class? Why can it be useful? Guru Question3. C++ does not support nested functions. That is, we cannot write something like: // Example 3
//
int f( int i )
{
int j = i*2; int g( int k )
{
return j+k;
} j += 4; return g( 3 );
} Demonstrate how it is possible to achieve the same effect in standard C++, and show how to generalize the solution. SolutionRecap: Nested and Local ClassesC++ provides many useful tools for information hiding and dependency management. As we recap nested classes and local classes, don't fixate too much on the syntax and semantics; rather, focus on how these features can be used to write solid, maintainable code and express good object designs -- designs that prefer weak coupling and strong cohesion. 1. What is a nested class? Why can it be useful? A nested class is a class enclosed within the scope of another class. For example: // Example 1: Nested class
//
class OuterClass
{
class NestedClass
{
// ...
}; // ...
}; Nested classes are useful for organizing code and controlling access and dependencies. Nested classes obey access rules just like other parts of a class do; so, in Example 1, if NestedClass is public then any code can name it as OuterClass::NestedClass. Often nested classes contain private implementation details, and are therefore made private; in Example 1, if NestedClass is private, then only OuterClass's members and friends can use NestedClass. Note that you can't get the same effect with namespaces alone, because namespaces merely group names into distinct areas. Namespaces do not by themselves provide access control, whereas classes do. So, if you want to control access rights to a class, one tool at your disposal is to nest it within another class. 2. What is a local class? Why can it be useful? A local class is a class defined within the scope of a function -- any function, whether a member function or a free function. For example: // Example 2: Local class
//
int f()
{
class LocalClass
{
// ...
}; // ...
}; Like nested classes, local classes can be a useful tool for managing code dependencies. In Example 2, only the code within f() itself is able to use LocalClass, which can be useful when LocalClass is, say, an internal implementation detail of f() that should never be exposed to other code. You can use a local class in most places where you can use a nonlocal class, but there is a major restriction to keep in mind: A local or unnamed class cannot be used as a template parameter. From the C++ standard (14.3.1/2): A local type, a type with no linkage, an unnamed
type or a type compounded from any of these types
shall not be used as a template-argument for a
template type-parameter. [Example: template <class T>
class X { /* ... */ }; void f()
{
struct S { /* ... */ };
X<S> x3; // error: local type used as
// template-argument
X<S*> x4; // error: pointer to local type
// used as template-argument
} --end example] Both nested classes and local classes are among C++'s many useful tools for information hiding and dependency management. Nested Functions: OverviewSome languages (but not C++) allow nested functions, which are similar in concept to nested classes. A nested function is defined inside another function (the "enclosing function"), such that: - the nested function has access to the enclosing function's variables; and - the nested function is local to the enclosing function, that is, it can't be called from elsewhere unless the enclosing function gives you a pointer to the nested function. Just as nested classes can be useful because they help control the visibility of a class, nested functions can be useful because they help control the visibility of a function. C++ does not have nested functions. But can we get the same effect? This brings us to the question: 3. C++ does not support nested functions. That is, we cannot write something like: // Example 3
//
int f( int i )
{
int j = i*2; int g( int k )
{
return j+k;
} j += 4; return g( 3 );
} Demonstrate how it is possible to achieve the same effect in standard C++, and show how to generalize the solution. Short answer: Yes, it is possible, with only a little code reorganization and a minor limitation. The basic idea is to turn a function into a functor -- and this discussion, not coincidentally, will also serve to nicely illustrate some of the power of functors. Attempts at Simulating Nested Functions in C++To solve a problem like Question #3 above, most people start out by trying something like the following: // Example 3(a): Naive "local functor"
// approach (doesn't work)
//
int f( int i )
{
int j = i*2; class g_
{
public:
int operator()( int k )
{
return j+k; // error: j isn't accessible
}
} g; j += 4; return g( 3 );
} In Example 3(a), the idea is to wrap the function in a local class, and call the function through a functor object. It's a nice idea, but it doesn't work for a simple reason: The local class object doesn't have access to the enclosing function's variables. "Well," one might say, "why don't we just give the local class pointers or references to all of the function's variables?" Indeed, that's usually the next attempt: // Example 3(b): Naive "local functor plus
// references to variables"
// approach (complex, fragile)
//
int f( int i )
{
int j = i*2; class g_
{
public:
g_( int& j ) : j_( j ) { } int operator()( int k )
{
return j_+k; // access j via a reference
} private:
int& j_;
} g( j ); j += 4; return g( 3 );
} Well, all right, I have to admit that this "works"... but only barely. This solution is fragile, difficult to extend, and can rightly be considered a hack. For example, consider that just to add a new variable requires four changes: a) add the variable; b) add a corresponding private reference to g_; c) add a corresponding constructor parameter to g_; and d) add a corresponding initialization to g_::g_(). That's not very maintainable. It also isn't easily extended to multiple local functions. Couldn't we do better? An Somewhat Improved SolutionWe can do better by moving the variables themselves into the local class: // Example 3(c): A better solution
//
int f( int i )
{
class g_
{
public:
int j; int operator()( int k )
{
return j+k;
}
} g; g.j = i*2;
g.j += 4; return g( 3 );
} This is a solid step forward. Now g_ doesn't need pointer or reference data members to point at external data, it doesn't need a constructor, and everything is much more natural all around. Note also that the technique is now extensible to any number of local functions, so let's add some more local functions called x(), y(), and z() while we're at it, and let's rename g_ to be more indicative of what the local class is actually doing: // Example 3(d): Nearly there!
//
int f( int i )
{
// Define a local functor that wraps all
// local data and functions.
//
class Local_
{
public:
int j; // All local functions go here:
//
int g( int k )
{
return j+k;
}
void x() { /* ... */ }
void y() { /* ... */ }
void z() { /* ... */ }
} local; local.j = i*2;
local.j += 4; local.x();
local.y();
local.z(); return local.g( 3 );
} This still has the problem that, if you need to initialize j using something other than default initialization, you have to add a clumsy constructor to the local class just to pass through the initialization value. The original question tried to initialize j to the value of i*2; here, we've had to create j and then assign the value, which could be difficult for more complex types. A Complete SolutionIf you don't need f itself to be a bona fide function (e.g., if you don't take pointers to it), you can turn the whole thing into a functor and elegantly support non-default initialization into the bargain: // Example 3(e): A complete and nicely
// extensible solution
//
class f
{
int retval; // f's "return value"
int j;
int g( int k ) { return j + k; };
void x() { /* ... */ }
void y() { /* ... */ }
void z() { /* ... */ }
public:
f( int i ) // original function, now a constructor
: j( i*2 )
{
j += 4;
x();
y();
z();
retval = g( 3 );
}
operator int() // returning the result
{
return retval;
}
}; The code is shown inline for brevity, but all of the private members could also be hidden behind a Pimpl, giving the same full separation of interface from implementation that we had with the original simple function. Note that this approach is easily extensible to member functions. For example, suppose that f() was a member function instead of a free function, and we would like to write a nested function g() inside f() as follows: // Example 4: This isn't legal C++, but it
// illustrates what we want: a local
// function inside a member function
//
class C
{
int data_;
public:
int f( int i )
{
// a hypothetical nested function
int g( int i ) { return data_ + i; } return g( data_ + i*2 );
}
}; We can express this by turning f() into a class as demonstrated in Example 3(e), except that whereas in Example 3(e) the class was at global scope it is now instead a nested class and accessed via a passthrough helper function: // Example 4(a): The complete and nicely
// extensible solution, now
// applied to a member function
//
class C
{
int data_;
friend class C_f;
public:
int f( int i );
}; class C_f
{
C* self;
int retval;
int g( int i ) { return self->data_ + i; }
public:
C_f( C* c, int i ) : self( c )
{
retval = g( self->data_ + i*2 );
}
operator int() { return retval; }
}; int C::f( int i ) { return C_f( this, i ); } SummaryThis approach illustrated above in Examples 3(e) and 4(a) simulates most of the features of local functions, and is easily extensible to any number of local functions. Its main weakness is that it requires that all variables be defined at the start of the 'function,' and doesn't (easily) allow us to declare variables closer to point of first use. It's also more tedious to write than a local function would be, and clearly a workaround for a language limitation. But, in the end, it's not bad, and it demonstrates some of the power inherent in functors over plain functions. The purpose of this GotW wasn't just to learn about how nice it might be to have local functions in C++. It was, as always, about setting a specific design problem, exploring alternative methods for solving it, and weighing the solutions to pick the best-engineered option. Along the way, we also got to experiment with various C++ features and see through use what makes functors useful. When designing your programs, strive for simplicity and elegance wherever possible. Some of the intermediate above solutions would arguably "work," yet should never be allowed to see the light of day in production code because they are complex, difficult to understand, and therefore difficult and expensive to maintain. Simpler designs are easier to code and test. Avoid complex solutions, which are almost certain to be fragile and harder to understand and maintain. While being careful not to fall into the trap of overengineering, do recognize that even if coming up with a judicious simple design takes a little extra time up front, the long-term benefits usually make that time well spent. Often putting in a little time now means saving more time later -- and most people would agree that the "more time later" is better spent with the family at the cottage than slaving away over a hot keyboard trying to find those last few bugs in a complicated rat's nest of code. |