为什么我可以用 C + + 在函数中定义结构和类?

我只是在 C + + 中错误地做了类似的事情,它工作了。为什么我可以这样做呢?

int main(int argc, char** argv) {
struct MyStruct
{
int somevalue;
};


MyStruct s;
s.somevalue = 5;
}

做完这个之后,我想起了很久以前在某个地方读到过这个技巧,作为一种穷人用于 C + + 的函数式编程工具,但是我不记得为什么这是有效的,或者我是在哪里读到的。

欢迎回答这两个问题!

注意: 虽然在写这个问题的时候我没有得到任何关于 这个问题的引用,但是当前的边栏指出了它,所以我把它放在这里作为参考,不管怎样,这个问题是不同的,但是可能是有用的。

72147 次浏览

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.

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".

[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.

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.

It's for making arrays of objects that are properly initialized.

I have a class C which has no default constructor. I want an array of objects of class C. I figure out how I want those objects initialized, then derive a class D from C with a static method which provides the argument for the C in D's default constructor:

#include <iostream>
using namespace std;


class C {
public:
C(int x) : mData(x)  {}
int method() { return mData; }
// ...
private:
int mData;
};


void f() {


// Here I am in f.  I need an array of 50 C objects starting with C(22)


class D : public C {
public:
D() : C(D::clicker()) {}
private:
// I want my C objects to be initialized with consecutive
// integers, starting at 22.
static int clicker() {
static int current = 22;
return current++;
}
};


D array[50] ;


// Now I will display the object in position 11 to verify it got initialized
// with the right value.


cout << "This should be 33: --> " << array[11].method() << endl;


cout << "sizodf(C): " << sizeof(C) << endl;
cout << "sizeof(D): " << sizeof(D) << endl;


return;


}


int main(int, char **) {
f();
return 0;
}

For the sake of simplicity, this example uses a trivial non-default constructor and a case where the values are known at compile time. It is straightforward to extend this technique to cases where you want an array of objects initialized with values that are known only at runtime.

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.
}