Por que posso definir estruturas e classes dentro de uma função em C ++?


93

Eu simplesmente fiz algo assim em C ++ por engano e funciona. Por que posso fazer isso?

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

    MyStruct s;
    s.somevalue = 5;
}

Depois de fazer isso, meio que me lembrei de ter lido sobre esse truque em algum lugar, há muito tempo, como uma espécie de ferramenta de programação funcional para C ++, mas não consigo me lembrar por que isso é válido, ou onde eu li.

As respostas a qualquer uma das perguntas são bem-vindas!

Nota: Embora ao escrever a pergunta eu não tenha obtido nenhuma referência a esta pergunta , a barra lateral atual indica isso, então irei colocá-la aqui para referência, de qualquer forma a pergunta é diferente, mas pode ser útil.


Respostas:


73

[EDITAR 18/4/2013]: Felizmente, a restrição mencionada abaixo foi removida no C ++ 11, portanto, as classes definidas localmente são úteis, afinal! Graças ao comentarista bamboon.

A capacidade de definir classes localmente tornaria a criação de functores personalizados (classes com operator()(), por exemplo, funções de comparação para passar para std::sort()ou "corpos de loop" para serem usados std::for_each()) muito mais conveniente.

Infelizmente, C ++ proíbe o uso de classes definidas localmente com modelos , pois eles não têm ligação. Uma vez que a maioria dos aplicativos de functores envolvem tipos de template que são modelados no tipo de functor, classes definidas localmente não podem ser usadas para isso - você deve defini-las fora da função. :(

[EDITAR 11/01/2009]

A citação relevante do padrão é:

14.3.1 / 2: .Um tipo local, um tipo sem ligação, um tipo não nomeado ou um tipo composto de qualquer um desses tipos não deve ser usado como um argumento-modelo para um parâmetro-tipo de modelo.


2
Embora empiricamente, isso parece funcionar com MSVC ++ 8. (Mas não com g ++.)
j_random_hacker

Estou usando o gcc 4.3.3 e parece funcionar lá: pastebin.com/f65b876b . Você tem uma referência de onde a norma o proíbe? Parece-me que pode ser facilmente instanciado no momento do uso.
Catskul

@Catskul: 14.3.1 / 2: "Um tipo local, um tipo sem ligação, um tipo sem nome ou um tipo composto de qualquer um desses tipos não deve ser usado como um argumento-modelo para um parâmetro-tipo de modelo". Acho que a lógica é que as classes locais exigiriam mais um monte de informações para serem colocadas em nomes mutilados, mas não tenho certeza disso. É claro que um compilador específico pode oferecer extensões para contornar isso, como parece que o MSVC ++ 8 e as versões recentes do g ++ fazem.
j_random_hacker

9
Essa restrição foi suspensa no C ++ 11.
Stephan Dollberg

31

Uma aplicação de classes C ++ definidas localmente é no padrão de design de fábrica :


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

Embora você possa fazer o mesmo com o namespace anônimo.


Interessante! Embora as restrições relacionadas aos modelos que mencionei se apliquem, essa abordagem garante que instâncias de Impl não possam ser criadas (ou mesmo comentadas!) Exceto por CreateBase (). Portanto, essa parece ser uma maneira excelente de reduzir a dependência dos clientes dos detalhes de implementação. +1.
j_random_hacker

26
É uma ideia legal, não tenho certeza se vou usá-la em breve, mas provavelmente uma boa ideia para tirar no bar e impressionar algumas garotas :)
Robert Gould

2
(Estou falando sobre as garotas BTW, não a resposta!)
markh44

9
lol Robert ... Sim, nada impressiona tanto uma mulher como saber sobre cantos obscuros de C ++ ...
j_random_hacker

10

Na verdade, é muito útil para fazer algum trabalho de segurança de exceção baseado em pilha. Ou limpeza geral de uma função com vários pontos de retorno. Isso é freqüentemente chamado de idioma RAII (aquisição de recursos é inicialização).

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();Acho que isso será uma declaração de função, em vez de uma definição de objeto.
usuário

2
@user Você está correto. Para chamar o construtor padrão, ele deve escrever Cleaner cleaner;ou Cleaner cleaner{};.
Callyalater

Classes dentro de funções não têm nada a ver com RAII e, além disso, este não é um código C ++ válido e não será compilado.
Mikhail Vasilyev

1
Mesmo dentro das funções, classes como essa são PRECISAMENTE do que se trata o RAII em C ++.
Christopher Bruns

9

Bem, basicamente, por que não? A structem C (voltando ao início dos tempos) era apenas uma forma de declarar uma estrutura de registro. Se você quiser um, por que não poder declará-lo onde declararia uma variável simples?

Depois de fazer isso, lembre-se de que o objetivo de C ++ era ser compatível com C, se possível. Então ficou.


tipo de recurso legal de ter sobrevivido, mas como j_random_hacker acabou de apontar, não é tão útil quanto eu imaginava em C ++: /
Robert Gould

Sim, as regras de escopo eram estranhas em C também. Acho que, agora que tenho mais de 25 anos de experiência com C ++, talvez seja um erro tentar ser tão parecido com C quanto eles eram. Por outro lado, linguagens mais elegantes como Eiffel não foram adotadas tão prontamente.
Charlie Martin

Sim, migrei uma base de código C existente para C ++ (mas não para Eiffel).
ChrisW


3

É para criar matrizes de objetos que são inicializados corretamente.

Eu tenho uma classe C que não tem construtor padrão. Quero um array de objetos da classe C. Descubro como quero esses objetos inicializados e, em seguida, derivar uma classe D de C com um método estático que fornece o argumento para o C no construtor padrão de D:

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

Para fins de simplicidade, este exemplo usa um construtor não padrão trivial e um caso em que os valores são conhecidos em tempo de compilação. É simples estender essa técnica aos casos em que você deseja uma matriz de objetos inicializada com valores que são conhecidos apenas em tempo de execução.


Certamente um aplicativo interessante! Não tenho certeza se é sábio ou mesmo seguro - se você precisar tratar esse array de D como um array de C (por exemplo, você precisa passá-lo para uma função que D*recebe um parâmetro), então isso será interrompido silenciosamente se D for realmente maior que C . (Eu acho ...)
j_random_hacker

+ j_random_hacker, sizeof (D) == sizeof (C). Eu adicionei um relatório sizeof () para você.
Thomas L Holaday
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.