为什么要在C ++中的函数中定义结构和类?


90

我只是错误地在C ++中做了这样的事情,并且它起作用了。我为什么可以这样做?

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

    MyStruct s;
    s.somevalue = 5;
}

现在,做完这些之后,我想起了很久以前在某个地方读到的有关此技巧的信息,它是一种针对C ++的穷人功能编程工具,但我不记得为什么有效,或者在哪里阅读。

欢迎回答任何一个问题!

注意:尽管在编写问题时我没有得到任何关于此问题的参考,但当前的侧边栏指出了这一点,因此我将其放在此处以供参考,无论哪种方式,问题都不同,但可能有用。


Answers:


70

[EDIT 18/4/2013]:令人高兴的是,以下提到的限制已在C ++ 11中解除,因此本地定义的类毕竟很有用!感谢评论者Bamboon。

定义类本地的能力让创建自定义函子(带类operator()(),如比较函数传递到std::sort()与其一起使用或“循环体” std::for_each()),就方便多了。

不幸的是,C ++禁止将本地定义的类与template一起使用,因为它们没有链接。由于函子的大多数应用程序都涉及以函子类型为模板的模板类型,因此不能将本地定义的类用于此目的-您必须在函数外部定义它们。:(

[编辑1/11/2009]

该标准的相关报价为:

14.3.1 / 2:。本地类型,无链接的类型,未命名的类型或从这些类型中的任何一种混合而成的类型均不得用作模板类型参数的模板参数。


2
尽管从经验上讲,这似乎适用于MSVC ++ 8。(但不适用于g ++。)
j_random_hacker,2009年

我正在使用gcc 4.3.3,并且似乎可以在其中工作:pastebin.com/f65b876b。您是否有参考该标准禁止的地方?在我看来,它可以在使用时轻松实例化。
Catskul's

@Catskul:14.3.1 / 2:“本地类型,没有链接的类型,未命名的类型或从这些类型中的任何一种混合而成的类型都不能用作模板类型参数的模板参数”。我猜想原因是本地类将需要另外一堆信息才能放入错误的名称中,但是我不确定这一点。当然,特定的编译器可能会提供扩展来解决此问题,就像MSVC ++ 8和最新版本的g ++一样。
j_random_hacker

9
在C ++ 11中取消了此限制。
Stephan Dollberg

31

本地定义的C ++类的一种应用是Factory设计模式


// 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;
}

虽然您可以使用匿名名称空间进行相同操作。


有趣!尽管我提到的有关模板的限制将适用,但这种方法可确保只能通过CreateBase()来创建(甚至谈论!)Impl实例。因此,这似乎是减少客户对实施细节的依赖程度的绝佳方法。+1。
j_random_hacker

26
这是一个好主意,不确定我是否会很快使用它,但是可能是一个很好的选择,可以在酒吧中吸引一些小鸡:)
罗伯特·古尔德

2
(我在说小鸡顺便说一句,不是答案!)
markh44

9
大声笑罗伯特...是的,没有什么比让女人印象深刻的要更了解C ++的晦涩之处...
j_random_hacker

10

实际上,这对于执行一些基于堆栈的异常安全工作非常有用。或从具有多个返回点的函数中进行常规清理。这通常称为RAII(资源获取是初始化)惯用语。

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

5
Cleaner cleaner();我认为这将是函数声明而不是对象定义。
用户

2
@用户您是正确的。要调用默认构造函数,他应编写Cleaner cleaner;Cleaner cleaner{};
callyalater '16

函数内部的类与RAII无关,此外,这不是有效的C ++代码,因此无法编译。
米哈伊尔·瓦西里耶夫

1
即使在函数内部,这样的类正是什么RAII在C ++是怎么一回事。
克里斯托弗·布伦斯

9

好吧,基本上,为什么不呢?structC语言中的A (回到时间的曙光)只是声明记录结构的一种方法。如果需要一个变量,为什么不能在声明一个简单变量的地方声明它呢?

完成此操作后,请记住C ++的目标是尽可能与C兼容。所以它留下了。


一种幸存的功能,但正如j_random_hacker所指出的那样,它没有我在C ++中想象的有用:/
Robert Gould

是的,范围定义规则在C语言中也很奇怪。我想,既然我在C ++已有25年以上的经验,那么努力像他们一样努力与C一样可能是一个错误。另一方面,像埃菲尔这样的更优雅的语言几乎没有那么容易被采用。
查理·马丁

是的,我已经将现有的C代码库迁移到C ++(但没有迁移到Eiffel)。
ChrisW


3

它用于制作正确初始化的对象数组。

我有没有默认构造函数的C类。我想要一个C类的对象数组。我弄清楚了如何初始化这些对象,然后使用静态方法从C派生一个D类,该方法为D的默认构造函数提供C的参数:

#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;
}

为简单起见,本示例使用一个琐碎的非默认构造函数以及在编译时知道值的情况。将这种技术扩展到希望使用仅在运行时才知道的值初始化对象数组的情况很简单。


当然是一个有趣的应用程序!但是,不确定它是否明智甚至安全-如果您需要将该D数组视为C数组(例如,您需要将其传递给带有D*参数的函数),那么如果D实际大于C,这将无声地中断(我认为...)
j_random_hacker 2013年

+ j_random_hacker,sizeof(D)== sizeof(C)。我为您添加了sizeof()报告。
Thomas L Holaday
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.