Inicialização de estrutura C ++ conveniente


141

Estou tentando encontrar uma maneira conveniente de inicializar estruturas C ++ 'pod'. Agora, considere a seguinte estrutura:

struct FooBar {
  int foo;
  float bar;
};
// just to make all examples work in C and C++:
typedef struct FooBar FooBar;

Se eu quiser inicializar isso convenientemente em C (!), Eu poderia simplesmente escrever:

/* A */ FooBar fb = { .foo = 12, .bar = 3.4 }; // illegal C++, legal C

Observe que quero evitar explicitamente a seguinte notação, porque me parece que foi feita para quebrar meu pescoço se eu mudar alguma coisa na estrutura no futuro:

/* B */ FooBar fb = { 12, 3.4 }; // legal C++, legal C, bad style?

Para obter o mesmo (ou pelo menos semelhante) em C ++ como no /* A */exemplo, eu teria que implementar um construtor idiota:

FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) {}
// ->
/* C */ FooBar fb(12, 3.4);

O que é bom para ferver água, mas não é adequado para pessoas preguiçosas (a preguiça é uma coisa boa, certo?). Além disso, é tão ruim quanto o /* B */exemplo, pois não indica explicitamente qual valor vai para qual membro.

Então, minha pergunta é basicamente como eu posso conseguir algo semelhante a /* A */ ou melhor em C ++? Como alternativa, eu ficaria bem com uma explicação sobre por que não gostaria de fazer isso (ou seja, por que meu paradigma mental é ruim).

EDITAR

Por conveniente , quero dizer também de manutenção e não redundante .


2
Eu acho que o exemplo B é o mais próximo que você vai chegar.
Marlon

2
Não vejo como o exemplo B é "mau estilo". Faz sentido para mim, já que você está inicializando cada membro por vez com seus respectivos valores.
Mike Bailey

26
Mike, é um estilo ruim, porque não está claro qual valor vai para qual membro. Você precisa examinar a definição da estrutura e, em seguida, contar os membros para descobrir o que cada valor significa.
Jnnnnn

9
Além disso, se a definição de FooBar mudar no futuro, a inicialização poderá ser interrompida.
Edward Falk

se a inicialização ficar longa e complexa, não se esqueça do padrão do construtor
sled

Respostas:


20

Inicializações designadas serão suportadas no c ++ 2a, mas você não precisa esperar, porque elas são suportadas oficialmente pelo GCC, Clang e MSVC.

#include <iostream>
#include <filesystem>

struct hello_world {
    const char* hello;
    const char* world;
};

int main () 
{
    hello_world hw = {
        .hello = "hello, ",
        .world = "world!"
    };

    std::cout << hw.hello << hw.world << std::endl;
    return 0;
}

GCC Demo MSVC Demo


Aviso: tenha em mente que, se você adicionar parâmetros ao final da estrutura posteriormente, as inicializações antigas ainda serão compiladas silenciosamente sem terem sido inicializadas.
Catskul 26/06/19

1
@Catskul No. Ele será inicializado com a lista de inicializadores vazia, o que resultará na inicialização com zero.
Ivaigult 27/06/19

Você está certo. Obrigado. Devo esclarecer, os parâmetros restantes serão silenciosamente efetivamente inicializados por padrão. O argumento que pretendi enfatizar foi que qualquer pessoa que espere que isso ajude a impor a inicialização explícita completa dos tipos de POD ficará decepcionada.
Catskul

43

Como style Anão é permitido em C ++ e você não deseja style B, que tal usar style BX:

FooBar fb = { /*.foo=*/ 12, /*.bar=*/ 3.4 };  // :)

Pelo menos ajuda até certo ponto.


8
+1: não garante realmente a inicialização correta (do POV do compilador), mas certamente ajuda o leitor ... embora os comentários devam ser mantidos em sincronia.
Matthieu M.

18
O comentário não impede que a inicialização da estrutura seja interrompida se eu inserir um novo campo entre fooe barno futuro. C ainda inicializaria os campos que queremos, mas C ++ não. E este é o ponto da questão - como obter o mesmo resultado em C ++. Quero dizer, o Python faz isso com argumentos nomeados, C - com campos "nomeados" e C ++ também deve ter algo, espero.
Dmitry_romanov

2
Comentários sincronizados? Me dá um tempo. A segurança passa pela janela. Reordene os parâmetros e a lança. Muito melhor com explicit FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) . Observe a palavra-chave explícita . Mesmo quebrar o padrão é melhor no que diz respeito à segurança. Em Clang: -Wno-c99-extensions
Daniel O

@ DanielW, não é sobre o que é melhor ou o que não é. esta resposta de acordo com o OP não desejar o estilo A (não c ++), B ou C, que abrange todos os casos válidos.
iammilind

@iammilind Acho que uma dica de por que o paradigma mental do OP é ruim pode melhorar a resposta. Eu considero isso perigoso como é agora.
21315 Daerst Das

10

Você poderia usar um lambda:

const FooBar fb = [&] {
    FooBar fb;
    fb.foo = 12;
    fb.bar = 3.4;
    return fb;
}();

Mais informações sobre esse idioma podem ser encontradas no blog de Herb Sutter .


1
Essa abordagem inicializa os campos duas vezes. Uma vez no construtor. O segundo é fb.XXX = YYY.
Dmytro Ovdiienko

9

Extraia os conteúdos em funções que os descrevam (refatoração básica):

FooBar fb = { foo(), bar() };

Eu sei que o estilo está muito próximo do que você não queria usar, mas permite a substituição mais fácil dos valores constantes e também os explica (portanto, não é necessário editar comentários), se eles mudarem.

Outra coisa que você pode fazer (já que você é preguiçoso) é tornar o construtor incorporado, para que você não precise digitar tanto (removendo "Foobar ::" e o tempo gasto alternando entre os arquivos he cpp):

struct FooBar {
  FooBar(int f, float b) : foo(f), bar(b) {}
  int foo;
  float bar;
};

1
Eu recomendo que qualquer pessoa que leia esta pergunta escolha o estilo no trecho de código inferior para esta resposta, se tudo o que você deseja fazer é poder inicializar rapidamente as estruturas com um conjunto de valores.
kayleeFrye_onDeck

8

Sua pergunta é um pouco difícil, porque até a função:

static FooBar MakeFooBar(int foo, float bar);

pode ser chamado como:

FooBar fb = MakeFooBar(3.4, 5);

devido às regras de promoção e conversões para tipos numéricos incorporados. (C nunca foi realmente fortemente digitado)

No C ++, o que você deseja é possível, embora com a ajuda de modelos e asserções estáticas:

template <typename Integer, typename Real>
FooBar MakeFooBar(Integer foo, Real bar) {
  static_assert(std::is_same<Integer, int>::value, "foo should be of type int");
  static_assert(std::is_same<Real, float>::value, "bar should be of type float");
  return { foo, bar };
}

Em C, você pode nomear os parâmetros, mas nunca irá além.

Por outro lado, se tudo o que você deseja é nomeado parâmetros, você escreve muito código complicado:

struct FooBarMaker {
  FooBarMaker(int f): _f(f) {}
  FooBar Bar(float b) const { return FooBar(_f, b); }
  int _f;
};

static FooBarMaker Foo(int f) { return FooBarMaker(f); }

// Usage
FooBar fb = Foo(5).Bar(3.4);

E você pode adicionar proteção de promoção de tipo, se quiser.


1
"Em C ++, o que você quer é alcançável": o OP não estava pedindo para ajudar a impedir a confusão da ordem dos parâmetros? Como o modelo que você propõe alcançaria isso? Apenas por simplicidade, digamos que temos 2 parâmetros, ambos int.
max

@max: impedirá isso apenas se os tipos diferirem (mesmo que sejam conversíveis entre si), que é a situação do OP. Se não consegue distinguir os tipos, é claro que não funciona, mas essa é outra questão.
Matthieu M.

Ah entendi. Sim, esses são dois problemas diferentes, e acho que o segundo não possui uma boa solução em C ++ no momento (mas parece que o C ++ 20 está adicionando suporte aos nomes de parâmetros no estilo C99 na inicialização agregada).
max

6

As interfaces C ++ de muitos compiladores (incluindo GCC e clang) entendem a sintaxe do inicializador C. Se você puder, basta usar esse método.


16
O que não é compatível com o padrão C ++!
bitmask

5
Eu sei que não é padrão. Mas se você pode usá-lo, ainda é a maneira mais sensata de inicializar uma estrutura.
Matthias Urlichs 01/01

2
Você pode proteger tipos de x e y, tornando privativo o construtor errado:private: FooBar(float x, int y) {};
dmitry_romanov

4
O clang (compilador c ++ baseado em llvm) também suporta essa sintaxe. Pena que não faz parte do padrão.
Nimrodm

Todos sabemos que os inicializadores C não fazem parte do padrão C ++. Mas muitos compiladores o entendem e a pergunta não diz qual compilador está sendo direcionado, se houver. Assim, por favor, não vote abaixo desta resposta.
Matthias Urlichs

4

Ainda outra maneira em C ++ é

struct Point
{
private:

 int x;
 int y;

public:
    Point& setX(int xIn) { x = Xin; return *this;}
    Point& setY(int yIn) { y = Yin; return *this;}

}

Point pt;
pt.setX(20).setY(20);

2
Incómodo para programação funcional (isto é, criar o objeto na lista de argumentos de uma chamada de função), mas, na verdade, é uma idéia interessante!
Bitmask

27
o otimizador provavelmente reduz, mas meus olhos não.
Matthieu M.

6
Duas palavras: argh ... argh! Como isso é melhor do que usar dados públicos com 'Point pt; pt.x = pt.y = 20; `? Ou se você deseja encapsulamento, como isso é melhor que um construtor?
OldPeculier

3
É melhor do que um construtor, porque você tem que olhar para a declaração de construtor para a ordem parâmetro ... é X, Y ou y, x, mas a forma como eu mostrei é evidente no site de chamada
parapura rajkumar

2
Isso não funciona se você deseja uma estrutura const. ou se você quiser dizer ao compilador para não permitir estruturas não inicializadas. Se você realmente deseja fazer desta maneira, pelo menos marque os levantadores com inline!
Matthias Urlichs

3

Opção D:

FooBar FooBarMake(int foo, float bar)

C legal, C ++ legal. Facilmente otimizável para PODs. Claro que não há argumentos nomeados, mas isso é como todo o C ++. Se você deseja argumentos nomeados, o Objetivo C deve ser a melhor escolha.

Opção E:

FooBar fb;
memset(&fb, 0, sizeof(FooBar));
fb.foo = 4;
fb.bar = 15.5f;

C legal, C ++ legal. Argumentos nomeados.


12
Em vez do memset que você pode usar FooBar fb = {};no C ++, ele inicializa por padrão todos os membros da estrutura.
Öö Tiib

@ ÖöTiib: Infelizmente, isso é ilegal C, no entanto.
CB Bailey

3

Eu sei que essa pergunta é antiga, mas há uma maneira de resolver isso até que o C ++ 20 finalmente traga esse recurso de C para C ++. O que você pode fazer para resolver isso é usar macros de pré-processador com static_asserts para verificar se a inicialização é válida. (Eu sei que as macros geralmente são ruins, mas aqui não vejo outra maneira.) Veja o código de exemplo abaixo:

#define INVALID_STRUCT_ERROR "Instantiation of struct failed: Type, order or number of attributes is wrong."

#define CREATE_STRUCT_1(type, identifier, m_1, p_1) \
{ p_1 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_2(type, identifier, m_1, p_1, m_2, p_2) \
{ p_1, p_2 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_4(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3, m_4, p_4) \
{ p_1, p_2, p_3, p_4 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_4) >= (offsetof(type, m_3) + sizeof(identifier.m_3)), INVALID_STRUCT_ERROR);\

// Create more macros for structs with more attributes...

Então, quando você tiver uma estrutura com atributos const, poderá fazer o seguinte:

struct MyStruct
{
    const int attr1;
    const float attr2;
    const double attr3;
};

const MyStruct test = CREATE_STRUCT_3(MyStruct, test, attr1, 1, attr2, 2.f, attr3, 3.);

É um pouco inconveniente, porque você precisa de macros para todos os números possíveis de atributos e precisa repetir o tipo e o nome da sua instância na chamada de macro. Além disso, você não pode usar a macro em uma declaração de retorno, porque as declarações vêm após a inicialização.

Mas isso resolve o seu problema: quando você altera a estrutura, a chamada falha no momento da compilação.

Se você usa C ++ 17, pode até tornar essas macros mais rigorosas forçando os mesmos tipos, por exemplo:

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_1) == typeid(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_2) == typeid(identifier.m_2), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_3) == typeid(identifier.m_3), INVALID_STRUCT_ERROR);\

Existe uma proposta em C ++ 20 para permitir os inicializadores nomeados?
Maël Nison 8/18


2

O caminho /* B */ é bom em C ++ e também o C ++ 0x estenderá a sintaxe, sendo útil também para contêineres em C ++. Eu não entendo por que você chama isso de estilo ruim?

Se você deseja indicar parâmetros com nomes, pode usar a biblioteca de parâmetros boost , mas isso pode confundir alguém não familiarizado com ela.

Reordenar membros de estrutura é como reordenar parâmetros de funções, essa refatoração pode causar problemas se você não fizer isso com muito cuidado.


7
Eu chamo isso de estilo ruim porque acho que é zero de manutenção. E se eu adicionar outro membro em um ano? Ou se eu alterar os pedidos / tipos de membros? Cada pedaço de código inicializado pode (muito provavelmente) quebrar.
Bitmask

2
@bitmask Mas, desde que você não tenha argumentos nomeados, você também precisará atualizar as chamadas de construtores e acho que muitas pessoas não acham que os construtores são um estilo ruim e inatingível. Também acho que a inicialização nomeada não é C, mas C99, da qual C ++ definitivamente não é um superconjunto.
Christian Rau

2
Se você adicionar outro membro em um ano ao final da estrutura, ele será inicializado por padrão no código já existente. Se você reordená-los, precisará editar todo o código existente, nada a fazer.
Öö Tiib

1
@bitmask: O primeiro exemplo seria "impossível de manter" também. O que acontece se você renomear uma variável na estrutura? Claro, você poderia substituir tudo, mas isso poderia renomear acidentalmente uma variável que não deveria ser renomeada.
Mike Bailey

@ChristianRau Desde quando C99 não é C? C não é o grupo e C99 uma versão específica / especificação ISO?
altendky

1

E essa sintaxe?

typedef struct
{
    int a;
    short b;
}
ABCD;

ABCD abc = { abc.a = 5, abc.b = 7 };

Acabei de testar em um Microsoft Visual C ++ 2015 e em g ++ 6.0.2. Trabalhando bem.
Você também pode criar uma macro específica se desejar evitar a duplicação do nome da variável.


clang++3.5.0-10 com -Weverything -std=c++1zparece confirmar isso. Mas não parece certo. Você sabe onde o padrão confirma que este é C ++ válido?
bitmask

Eu não sei, mas eu usei isso em diferentes compiladores desde muito tempo atrás e não vi nenhum problema. Agora testado no g ++ 4.4.7 - funciona bem.
cls

5
Eu não acho que isso funcione. Tente ABCD abc = { abc.b = 7, abc.a = 5 };.
Raymai97 #

@deselect, funciona porque o campo é inicializado com valor, retornado pelo operador =. Então, na verdade você inicializa o aluno duas vezes.
Dmytro Ovdiienko

1

Para mim, a maneira mais preguiçosa de permitir a inicialização em linha é usar essa macro.

#define METHOD_MEMBER(TYPE, NAME, CLASS) \
CLASS &set_ ## NAME(const TYPE &_val) { NAME = _val; return *this; } \
TYPE NAME;

struct foo {
    METHOD_MEMBER(string, attr1, foo)
    METHOD_MEMBER(int, attr2, foo)
    METHOD_MEMBER(double, attr3, foo)
};

// inline usage
foo test = foo().set_attr1("hi").set_attr2(22).set_attr3(3.14);

Essa macro cria atributo e método de auto-referência.

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.