Why can I define structures and classes within a function in C++?

67,994

Solution 1

[EDIT 18/4/2013]: Happily, the restriction mentioned below has been lifted in C++11, so locally defined classes are useful after all! Thanks to commenter bamboon.

The ability to define classes locally would make creating custom functors (classes with an operator()(), e.g. comparison functions for passing to std::sort() or "loop bodies" to be used with std::for_each()) much more convenient.

Unfortunately, C++ forbids using locally-defined classes with templates, as they have no linkage. Since most applications of functors involve template types that are templated on the functor type, locally defined classes can't be used for this -- you must define them outside the function. :(

[EDIT 1/11/2009]

The relevant quote from the standard is:

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.

Solution 2

One application of locally-defined C++ classes is in Factory design pattern:


// In some header
class Base
{
public:
    virtual ~Base() {}
    virtual void DoStuff() = 0;
};

Base* CreateBase( const Param& );

// in some .cpp file
Base* CreateBase( const Params& p )
{
    struct Impl: Base
    {
        virtual void DoStuff() { ... }
    };

    ...
    return new Impl;
}

Though you can do the same with anonymous namespace.

Solution 3

It's actually very useful for doing some stack-based exception-safety work. Or general cleanup from a function with multiple return points. This is often called the RAII (resource acquisition is initialzation) idiom.

void function()
{

    struct Cleaner
    {
        Cleaner()
        {
            // do some initialization code in here
            // maybe start some transaction, or acquire a mutex or something
        }

        ~Cleaner()
        {
             // do the associated cleanup
             // (commit your transaction, release your mutex, etc.)
        }
    };

    Cleaner cleaner;

    // Now do something really dangerous
    // But you know that even in the case of an uncaught exception, 
    // ~Cleaner will be called.

    // Or alternatively, write some ill-advised code with multiple return points here.
    // No matter where you return from the function ~Cleaner will be called.
}

Solution 4

Well, basically, why not? A struct in C (going back to the dawn of time) was just a way to declare a record structure. If you want one, why not be able to declare it where you would declare a simple variable?

Once you do that, then remember that a goal of C++ was to be compatible with C if at all possible. So it stayed.

Solution 5

It's mentioned at, for example, section "7.8: Local classes: classes inside functions" of http://www.icce.rug.nl/documents/cplusplus/cplusplus07.html which calls it a "local class" and says it "can be very useful in advanced applications involving inheritance or templates".

Share:
67,994
Robert Gould
Author by

Robert Gould

Master Server Engineer & Game Developer, building MMOs, mobile games and social games for a few millions of users over the last few years. Currently working with Node.js, Actionscript, Redis, and other fun stuff. Specialist in programming languages, scripting engines, analytics, concurrency, lock-free programming, networks, persistence and data-driven game development.

Updated on November 09, 2020

Comments

  • Robert Gould
    Robert Gould over 3 years

    I just mistakenly did something like this in C++, and it works. Why can I do this?

    int main(int argc, char** argv) {
        struct MyStruct
        {
          int somevalue;
        };
    
        MyStruct s;
        s.somevalue = 5;
    }
    

    Now after doing this, I kind of remembered reading about this trick someplace, a long time ago, as a kind of poor-man's functional programming tool for C++, but I can't remember why this is valid, or where I read it.

    Answers to either question are welcome!

    Note: Although when writing the question I didn't get any references to this question, the current side-bar points it out so I'll put it here for reference, either way the question is different but might be useful.

  • Robert Gould
    Robert Gould almost 15 years
    kind of an neat feature to have survived, but as j_random_hacker just pointed out it's not as useful as I was imagining in C++ :/
  • Ankit Roy
    Ankit Roy almost 15 years
    Although empirically, this seems to work with MSVC++8. (But not with g++.)
  • Charlie Martin
    Charlie Martin almost 15 years
    Yeah, the scoping rules were weird in C too. I think, now that I've 25+ years experience with C++, that maybe striving to be as much like C as they did might have been a mistake. On the other hand, more elegant languages like Eiffel weren't adopted nearly as readily.
  • Ankit Roy
    Ankit Roy almost 15 years
    Interesting! Although the restrictions regarding templates I mention will apply, this approach guarantees that instances of Impl can't be created (or even talked about!) except by CreateBase(). So this seems like an excellent way to reduce the extent to which clients depend on implementation details. +1.
  • Robert Gould
    Robert Gould almost 15 years
    That's a neat idea, not sure if I'll be using it anytime soon, but probably a good one to pull out at the bar to impress some chicks :)
  • ChrisW
    ChrisW almost 15 years
    Yes, I've migrated an existing C codebase to C++ (but not to Eiffel).
  • markh44
    markh44 almost 15 years
    (I'm talking about the chicks BTW, not the answer!)
  • Ankit Roy
    Ankit Roy almost 15 years
    lol Robert... Yeah, nothing quite impresses a woman like knowing about obscure corners of C++...
  • Catskul
    Catskul over 14 years
    Im using gcc 4.3.3, and it seems to work there: pastebin.com/f65b876b. Do you have a reference to where the standard forbids it? It seems to me that it could easily be instantiated at the time of use.
  • Ankit Roy
    Ankit Roy over 14 years
    @Catskul: 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". I guess the rationale is that local classes would require yet another bunch of information to be pushed into mangled names, but I don't know that for sure. Of course a particular compiler may offer extensions to get around this, as it seems MSVC++8 and recent versions of g++ do.
  • Ankit Roy
    Ankit Roy about 11 years
    Certainly an interesting application! Not sure it's wise or even safe though -- if you need to treat that array of D as an array of C (e.g. you need to pass it to a function taking a D* parameter) then this will silently break if D is actually larger than C. (I think...)
  • Thomas L Holaday
    Thomas L Holaday about 11 years
    +j_random_hacker, sizeof(D) == sizeof(C). I added a sizeof() report for you.
  • Stephan Dollberg
    Stephan Dollberg about 11 years
    This restriction was lifted in C++11.
  • user
    user over 8 years
    Cleaner cleaner(); I think this will be function declaration rather than a object definition.
  • callyalater
    callyalater about 8 years
    @user You are correct. To call the default constructor, he should write Cleaner cleaner; or Cleaner cleaner{};.
  • Mikhail Vasilyev
    Mikhail Vasilyev over 5 years
    Classes inside functions have nothing to do with RAII and, besides, this is not a valid C++ code and will not compile.
  • Christopher Bruns
    Christopher Bruns over 4 years
    Even inside functions, classes like this are PRECISELY what RAII in C++ is all about.