Inicialização da Estrutura C ++

278

É possível inicializar estruturas em C ++, como indicado abaixo

struct address {
    int street_no;
    char *street_name;
    char *city;
    char *prov;
    char *postal_code;
};
address temp_address =
    { .city = "Hamilton", .prov = "Ontario" };

Os links aqui e aqui mencionam que é possível usar esse estilo somente em C. Se sim, por que isso não é possível em C ++? Existe alguma razão técnica subjacente para que ele não seja implementado em C ++ ou é uma má prática usar esse estilo. Eu gosto de usar essa maneira de inicializar porque minha estrutura é grande e esse estilo me dá uma legibilidade clara de qual valor é atribuído a qual membro.

Por favor, compartilhe comigo se houver outras maneiras pelas quais possamos obter a mesma legibilidade.

Consultei os seguintes links antes de postar esta pergunta

  1. C / C ++ para AIX
  2. Inicialização da estrutura C com variável
  3. Inicialização de estrutura estática com tags em C ++
  4. Inicialização de estrutura adequada do C ++ 11
Dinesh PR
fonte
20
Visão pessoal do mundo: você não precisa desse estilo de inicialização de objeto em C ++, porque deveria usar um construtor.
Philip Kendall
7
Sim, pensei nisso, mas tenho uma grande variedade de estruturas. Seria fácil e legível para mim usar dessa maneira. Você tem algum estilo / boa prática de inicializar usando o Constructor, que também oferece melhor legibilidade.
Dinesh PR
2
Você considerou a biblioteca de parâmetros boost, em combinação com os construtores? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy
18
Não é tão relacionado à programação: esse endereço funciona bem apenas nos EUA. Na França, não temos uma "província", em outras partes do mundo, não há código postal, a avó de uma amiga mora em uma vila tão pequena que seu endereço é "Senhora X, código postal" nome da pequena aldeia "(sim, sem rua). Portanto, considere com cuidado o que é um endereço válido para o mercado no qual você o aplicará;)
Matthieu M.
5
@MatthieuM. Não há províncias nos EUA (pode ser um formato canadense?), Mas existem estados, territórios e até pequenas aldeias que não se dão ao trabalho de nomear ruas. Portanto, a questão da conformidade do endereço se aplica até aqui.
Tim

Respostas:

165

Se você quiser deixar claro qual é o valor de cada inicializador, apenas divida-o em várias linhas, com um comentário em cada um:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};
Wyzard
fonte
7
Eu pessoalmente gosto e recomendo este estilo
Dinesh PR
29
Qual é a diferença entre fazer isso e realmente usar a notação de ponto para acessar MAIS PRECISAMENTE o próprio campo, não é como se você estivesse economizando espaço, se é essa a preocupação. Eu realmente não entendo programadores de C ++ quando se trata de ser consistente e escrever código de manutenção, eles parecem sempre querer fazer algo diferente para destacar seu código, o código deve refletir o problema que está sendo resolvido, não deve ser um idioma por si só, busca confiabilidade e facilidade de manutenção.
5
@ user1043000 bem, por exemplo, neste caso, a ordem em que você coloca seus membros é da maior importância. Se você adicionar um campo no meio da sua estrutura, precisará voltar a esse código e procurar o local exato para inserir sua nova inicialização, o que é difícil e chato. Com a notação de ponto, você pode simplesmente colocar sua nova inicialização no final da lista sem se preocupar com o pedido. E a notação de ponto é muito mais segura se você adicionar o mesmo tipo (como char*) de um dos outros membros acima ou abaixo na estrutura, porque não há risco de trocá-los.
Gui13
6
comentário de orip. Se a definição da estrutura de dados for alterada e ninguém pensar em procurar as inicializações, ou não conseguir encontrá-las todas ou cometer um erro ao editá-las, as coisas vão desmoronar.
Edward Falk
3
A maioria das estruturas POSIX (se não todas) não tem uma ordem definida, apenas membros definidos. (struct timeval){ .seconds = 0, .microseconds = 100 }sempre será cem microssegundos, mas timeval { 0, 100 }pode ser cem SEGUNDOS . Você não quer encontrar algo assim da maneira mais difícil.
yyny 14/09/18
98

Depois que minha pergunta não resultou em um resultado satisfatório (porque o C ++ não implementa init baseado em tags para estruturas), segui o truque que encontrei aqui: Os membros de uma estrutura C ++ são inicializados como 0 por padrão?

Para você, isso equivaleria a:

address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";

Este é certamente o mais próximo do que você queria originalmente (zere todos os campos, exceto aqueles que você deseja inicializar).

Gui13
fonte
10
Isso não funciona para objetos estaticamente não inicializados
#
5
static address temp_address = {};vai funcionar. Preenchê-lo depois depende do tempo de execução, sim. Você pode ignorar isso, fornecendo uma função estática que faz a inicialização para você: static address temp_address = init_my_temp_address();.
Gui13
No C ++ 11, init_my_temp_addresspode ser uma função lambda:static address temp_address = [] () { /* initialization code */ }();
dureuill
3
Má ideia, viola o princípio RAII.
Hxpax #
2
Uma péssima idéia: adicione um membro ao seu addresse você nunca saberá de todos os lugares que criam um addresse agora não inicializam seu novo membro.
Myster_doctor
24

Como outros já mencionaram, isso é designado como inicializador.

Esse recurso faz parte do C ++ 20

sameer chaudhari
fonte
17

Os identificadores de campo são de fato a sintaxe do inicializador C. Em C ++, basta fornecer os valores na ordem correta, sem os nomes dos campos. Infelizmente, isso significa que você precisa fornecer todos eles (na verdade, você pode omitir os campos com valor zero à direita e o resultado será o mesmo):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 
Gene
fonte
1
Sim, você sempre pode usar a inicialização alinhada da estrutura.
texasbruce
3
Sim, atualmente estou usando apenas este método (inicialização estrutural alinhada). Mas sinto que a legibilidade não é boa. Como minha estrutura é grande, o inicializador possui muitos dados e é difícil rastrear qual valor é atribuído a qual membro.
Dinesh PR
7
@ DineshP.R. Então escreva um construtor!
Sr. Lister
2
@ MrLister (ou alguém) Talvez eu esteja preso em uma nuvem de estupidez no momento, mas lembre-se de explicar como um construtor seria muito melhor? Parece-me que há pouca diferença entre fornecer um monte de valores sem nome dependentes de ordem a uma lista de inicializadores ou fornecer um monte de valores sem nome dependentes de ordem a um construtor ...?
Yan
1
@ yano Para ser sincero, não me lembro por que pensei que um construtor seria a resposta para o problema. Se bem me lembro, voltarei para você.
Sr. Lister
13

Esse recurso é chamado de inicializadores designados . É uma adição ao padrão C99. No entanto, esse recurso foi deixado de fora do C ++ 11. De acordo com a linguagem de programação C ++, 4ª edição, seção 44.3.3.2 (recursos C não adotados pelo C ++):

Algumas adições ao C99 (em comparação com o C89) não foram adotadas deliberadamente no C ++:

[1] Matrizes de comprimento variável (VLAs); use vetor ou alguma forma de matriz dinâmica

[2] Inicializadores designados; use construtores

A gramática C99 possui os inicializadores designados [Ver ISO / IEC 9899: 2011, N1570 Committee Draft - 12 de abril de 2011]

6.7.9 Inicialização

initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
initializer-list:
    designation_opt initializer
    initializer-list , designationopt initializer
designation:
    designator-list =
designator-list:
    designator
    designator-list designator
designator:
    [ constant-expression ]
    . identifier

Por outro lado, o C ++ 11 não possui os inicializadores designados [Ver ISO / IEC 14882: 2011, N3690 Committee Draft - 15 de maio de 2013]

8.5 Inicializadores

initializer:
    brace-or-equal-initializer
    ( expression-list )
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ...opt
    initializer-list , initializer-clause ...opt
braced-init-list:
    { initializer-list ,opt }
    { }

Para obter o mesmo efeito, use construtores ou listas de inicializadores:

Guilherme Ferreira
fonte
9

Você pode apenas inicializar via ctor:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};
nikc
fonte
9
Este é o caso apenas se você controlar a definição de struct address. Além disso, os tipos de POD geralmente não têm intencionalmente construtor e destruidor.
user4815162342
7

Você pode até empacotar a solução do Gui13 em uma única declaração de inicialização:

struct address {
                 int street_no;
                 char *street_name;
                 char *city;
                 char *prov;
                 char *postal_code;
               };


address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);

Isenção de responsabilidade: eu não recomendo este estilo

user396672
fonte
Isso ainda é perigoso, pois permite adicionar um membro addresse o código ainda será compilado com um milhão de lugares, inicializando apenas os cinco membros originais. A melhor parte de inicialização struct é que você pode ter todos os membros conste que irá forçá-lo para inicializar todos eles
mystery_doctor
7

Eu sei que essa pergunta é bastante antiga, mas encontrei outra maneira de inicializar, usando constexpr e currying:

struct mp_struct_t {
    public:
        constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
        constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
        constexpr mp_struct_t another_member(int member) { return {member1, member,     member2}; }
        constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }

    int member1, member2, member3;
};

static mp_struct_t a_struct = mp_struct_t{1}
                           .another_member(2)
                           .yet_another_one(3);

Este método também funciona para variáveis ​​estáticas globais e até mesmo para as que são comuns. A única desvantagem é a má manutenção: sempre que outro membro precisar ser inicializado usando esse método, todos os métodos de inicialização de membros deverão ser alterados.

Fabian
fonte
1
Este é o padrão do construtor . Os métodos de membros pode retornar uma referência para a propriedade a ser modificada em vez de criar uma nova estrutura de cada vez
phuclv
6

Eu posso estar perdendo alguma coisa aqui, por que não:

#include <cstdio>    
struct Group {
    int x;
    int y;
    const char* s;
};

int main() 
{  
  Group group {
    .x = 1, 
    .y = 2, 
    .s = "Hello it works"
  };
  printf("%d, %d, %s", group.x, group.y, group.s);
}
run_the_race
fonte
Compilei o programa acima com um compilador MinGW C ++ e um compilador Arduino AVR C ++, e ambos foram executados conforme o esperado. Observe o #include <cstdio>
run_the_race
4
@run_the_race, trata-se do que o padrão c ++ não diz sobre o comportamento de um determinado compilador. No entanto, esse recurso está disponível no c ++ 20.
Jon Jon
isso só funciona se a estrutura for POD. Portanto, ele irá parar de compilar se você adicionar um construtor a ele.
oromoiluig 16/06
5

Não é implementado em C ++. (também, char*cordas? Espero que não).

Geralmente, se você tem tantos parâmetros, é um cheiro de código bastante sério. Mas, em vez disso, por que não simplesmente inicializar com valor a estrutura e depois atribuir cada membro?

Cachorro
fonte
6
"(também, char*cordas? Espero que não)." Bem, é um exemplo em C.
Ed S.
não podemos usar char * em C ++? Atualmente estou usando e está funcionando (pode estar fazendo algo errado). Minha suposição é que o compilador criará seqüências constantes de "Hamilton" e "Ontario" e atribuirá seu endereço aos membros da estrutura. Será correto usar const char *?
Dinesh PR
8
Você pode usar, char*mas const char*é muito mais seguro para o tipo e todo mundo usa, std::stringporque é muito mais confiável.
Filhote de cachorro
Está bem. Quando li "como mencionado abaixo", presumi que fosse um exemplo copiado de algum lugar.
Ed S.
2

Eu encontrei essa maneira de fazer isso para variáveis ​​globais, que não precisam modificar a definição da estrutura original:

struct address {
             int street_no;
             char *street_name;
             char *city;
             char *prov;
             char *postal_code;
           };

declare a variável de um novo tipo herdada do tipo de estrutura original e use o construtor para a inicialização dos campos:

struct temp_address : address { temp_address() { 
    city = "Hamilton"; 
    prov = "Ontario"; 
} } temp_address;

Não é tão elegante quanto o estilo C ...

Para uma variável local, é necessário um conjunto memset adicional (this, 0, sizeof (* this)) no início do construtor, portanto, claramente não é pior e a resposta de @ gui13 é mais apropriada.

(Observe que 'temp_address' é uma variável do tipo 'temp_address'; no entanto, esse novo tipo é herdado de 'address' e pode ser usado em todos os locais em que 'address' é esperado, portanto, está OK.)

VincentP
fonte
1

Hoje enfrentei um problema semelhante, onde tenho uma estrutura que quero preencher com dados de teste que serão passados ​​como argumentos para uma função que estou testando. Eu queria ter um vetor dessas estruturas e estava procurando um método de uma linha para inicializar cada estrutura.

Acabei indo com uma função construtora na estrutura, que acredito que também foi sugerida em algumas respostas à sua pergunta.

Provavelmente, é uma má prática ter os argumentos para o construtor com os mesmos nomes que as variáveis ​​públicas do membro, exigindo o uso do thisponteiro. Alguém pode sugerir uma edição, se houver uma maneira melhor.

typedef struct testdatum_s {
    public:
    std::string argument1;
    std::string argument2;
    std::string argument3;
    std::string argument4;
    int count;

    testdatum_s (
        std::string argument1,
        std::string argument2,
        std::string argument3,
        std::string argument4,
        int count)
    {
        this->rotation = argument1;
        this->tstamp = argument2;
        this->auth = argument3;
        this->answer = argument4;
        this->count = count;
    }

} testdatum;

Que eu usei na minha função de teste para chamar a função sendo testada com vários argumentos como este:

std::vector<testdatum> testdata;

testdata.push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.push_back(testdatum("val31", "val32", "val33", "val34", 7));

for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
    function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}
Unacoder
fonte
1

É possível, mas apenas se a estrutura que você está inicializando for uma estrutura POD (dados antigos simples). Ele não pode conter nenhum método, construtor ou mesmo valor padrão.

quem sabe
fonte
1

No C ++, os inicializadores do estilo C foram substituídos por construtores que, durante o tempo de compilação, podem garantir que apenas as inicializações válidas sejam realizadas (ou seja, após a inicialização, os membros do objeto são consistentes).

É uma boa prática, mas às vezes uma pré-inicialização é útil, como no seu exemplo. OOP resolve isso por classes abstratas ou padrões de design criacionais .

Na minha opinião, o uso dessa maneira segura reduz a simplicidade e, às vezes, a troca de segurança pode ser muito cara, pois o código simples não precisa de design sofisticado para manter a manutenção.

Como solução alternativa, sugiro definir macros usando lambdas para simplificar a inicialização para se parecer quase com o estilo C:

struct address {
  int street_no;
  const char *street_name;
  const char *city;
  const char *prov;
  const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE

A macro ADDRESS se expande para

[] { address _={}; /* definition... */ ; return _; }()

que cria e chama o lambda. Os parâmetros de macro também são separados por vírgula; portanto, você precisa colocar o inicializador entre colchetes e chamar como

address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));

Você também pode escrever inicializador de macro generalizado

#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE

mas a chamada é um pouco menos bonita

address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));

no entanto, você pode definir a macro ADDRESS usando a macro INIT geral facilmente

#define ADDRESS(x) INIT(address,x)
Jan Turoň
fonte
1

No GNUC ++ (parece obsoleto desde a versão 2.5, há muito tempo :) :) Veja as respostas aqui: Inicialização C struct usando rótulos. Funciona, mas como? ), é possível inicializar uma estrutura como esta:

struct inventory_item {
    int bananas;
    int apples;
    int pineapples;
};

inventory_item first_item = {
    bananas: 2,
    apples: 49,
    pineapples: 4
};
O menino da ciência
fonte
0

Inspirado por esta resposta realmente elegante: ( https://stackoverflow.com/a/49572324/4808079 )

Você pode fazer fechamentos de lamba:

// Nobody wants to remember the order of these things
struct SomeBigStruct {
  int min = 1;
  int mean = 3 ;
  int mode = 5;
  int max = 10;
  string name;
  string nickname;
  ... // the list goes on
}

.

class SomeClass {
  static const inline SomeBigStruct voiceAmps = []{
    ModulationTarget $ {};
    $.min = 0;  
    $.nickname = "Bobby";
    $.bloodtype = "O-";
    return $;
  }();
}

Ou, se você quiser ser muito chique

#define DesignatedInit(T, ...)\
  []{ T ${}; __VA_ARGS__; return $; }()

class SomeClass {
  static const inline SomeBigStruct voiceAmps = DesignatedInit(
    ModulationTarget,
    $.min = 0,
    $.nickname = "Bobby",
    $.bloodtype = "O-",
  );
}

Existem algumas desvantagens envolvidas, principalmente relacionadas a membros não inicializados. Pelo que dizem os comentários das respostas vinculadas, ele é compilado com eficiência, embora eu não o tenha testado.

No geral, acho que é uma abordagem interessante.

Seph Reed
fonte