A empresa em que trabalho está inicializando todas as estruturas de dados por meio de uma função de inicialização da seguinte maneira:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
InitializeFoo(Foo* const foo){
foo->a = x; //derived here based on other data
foo->b = y; //derived here based on other data
foo->c = z; //derived here based on other data
}
//initializing the structure
Foo foo;
InitializeFoo(&foo);
Eu recebi um empurrão para trás tentando inicializar minhas estruturas assim:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
Foo ConstructFoo(int a, int b, int c){
Foo foo;
foo.a = a; //part of parameter input (inputs derived outside of function)
foo.b = b; //part of parameter input (inputs derived outside of function)
foo.c = c; //part of parameter input (inputs derived outside of function)
return foo;
}
//initialize (or construct) the structure
Foo foo = ConstructFoo(x,y,z);
Existe uma vantagem para um sobre o outro?
Qual deles devo fazer e como justificaria isso como uma prática melhor?
c
coding-style
constructors
initialization
Trevor Hickey
fonte
fonte
InitializeFoo()
é um construtor. A única diferença de um construtor C ++ é que othis
ponteiro é passado explicitamente e não implicitamente. O código compiladoInitializeFoo()
e um C ++ correspondenteFoo::Foo()
são exatamente iguais.Respostas:
Na segunda abordagem, você nunca terá um Foo semi-inicializado. Colocar toda a construção em um só lugar parece um lugar mais sensato e óbvio.
Mas ... a 1ª via não é tão ruim e é frequentemente usada em muitas áreas (há até uma discussão sobre a melhor maneira de injetar dependências, injeção de propriedades como sua primeira via ou injeção de construtores como a segunda) . Nem está errado.
Portanto, se nenhum dos dois estiver errado e o resto da empresa usar a abordagem nº 1, você deverá se ajustar à base de código existente e não tentar atrapalhar a introdução de um novo padrão. Este é realmente o fator mais importante em jogo aqui, seja agradável com seus novos amigos e não tente ser aquele floco de neve especial que faz as coisas de maneira diferente.
fonte
void SetupFoo(Foo *out, int a, int b, int c)
Foo
? A primeira abordagem também realiza toda a inicialização em um único local. (Ou você está considerando uma un inicializadoFoo
struct para ser "meio-inicializado"?)Ambas as abordagens agrupam o código de inicialização em uma única chamada de função. Por enquanto, tudo bem.
No entanto, existem dois problemas com a segunda abordagem:
O segundo não constrói o objeto resultante, ele inicializa outro objeto na pilha, que é copiado para o objeto final. É por isso que eu consideraria a segunda abordagem um pouco inferior. A resposta que você recebeu provavelmente deve-se a esta cópia estranha.
Isto é ainda pior quando você derivar uma classe
Derived
deFoo
(estruturas são largamente utilizados para orientação a objetos em C): Com a segunda abordagem, a funçãoConstructDerived()
chamariaConstructFoo()
, copie o temporária resultandoFoo
objeto sobre na ranhura superclasse de umDerived
objeto; terminar a inicialização doDerived
objeto; apenas para que o objeto resultante seja copiado novamente no retorno. Adicione uma terceira camada, e a coisa toda se torna completamente ridícula.Com a segunda abordagem, as
ConstructClass()
funções não têm acesso ao endereço do objeto em construção. Isso torna impossível vincular objetos durante a construção, pois é necessário quando um objeto precisa se registrar com outro objeto para um retorno de chamada.Finalmente, nem todas
structs
são classes de pleno direito. Algunsstructs
efetivamente agrupam várias variáveis, sem nenhuma restrição interna aos valores dessas variáveis.typedef struct Point { int x, y; } Point;
seria um bom exemplo disso. Para estes, o uso de uma função inicializadora parece um exagero. Nesses casos, a sintaxe literal composta pode ser conveniente (é C99):ou
fonte
int x = 3;
não armazenar a stringx
no binário. O endereço e os pontos de herança são bons; o suposto fracasso da elisão é tolice.unsigned char
não permita otimizações para as quais o A regra estrita de aliasing seria insuficiente, ao mesmo tempo em que tornava as expectativas do programador mais claras.Dependendo do conteúdo da estrutura e do compilador específico que está sendo usado, qualquer uma das abordagens pode ser mais rápida. Um padrão típico é que estruturas que atendem a certos critérios podem ser retornadas em registros; para funções que retornam outros tipos de estrutura, o chamador deve alocar espaço para a estrutura temporária em algum lugar (geralmente na pilha) e transmitir seu endereço como um parâmetro "oculto"; nos casos em que o retorno de uma função é armazenado diretamente em uma variável local cujo endereço não é mantido por nenhum código externo, alguns compiladores podem passar o endereço dessa variável diretamente.
Se um tipo de estrutura satisfaz os requisitos de uma determinada implementação a serem retornados nos registros (por exemplo, não sendo maior que uma palavra-máquina ou preenchendo precisamente duas palavras-máquina) com uma função de retorno, a estrutura pode ser mais rápida do que passar o endereço de uma estrutura, especialmente como a exposição do endereço de uma variável ao código externo que pode manter uma cópia dela pode impedir algumas otimizações úteis. Se um tipo não atender a esses requisitos, o código gerado para uma função que retorna uma estrutura será semelhante ao de uma função que aceita um ponteiro de destino; o código de chamada provavelmente seria mais rápido para o formulário que usa um ponteiro, mas esse formulário perde algumas oportunidades de otimização.
É uma pena que C não forneça um meio de dizer que uma função é proibida de manter uma cópia de um ponteiro passado (semântica semelhante a uma referência C ++), pois a passagem de um ponteiro restrito obteria as vantagens diretas de desempenho da passagem um ponteiro para um objeto preexistente, mas, ao mesmo tempo, evite os custos semânticos de exigir que um compilador considere "exposto" o endereço de uma variável.
fonte
Um argumento a favor do estilo "parâmetro de saída" é que ele permite que a função retorne um código de erro.
Considerando um conjunto de estruturas relacionadas, se a inicialização puder falhar para qualquer uma delas, pode valer a pena que todas elas usem o estilo out-parameter por uma questão de consistência.
fonte
errno
.Presumo que seu foco esteja na inicialização via parâmetro de saída vs. inicialização via retorno, não na discrepância de como os argumentos de construção são fornecidos.
Observe que a primeira abordagem pode permitir
Foo
ser opaca (embora não seja da maneira que você a usa atualmente), e isso geralmente é desejável para manutenção a longo prazo. Você pode considerar, por exemplo, uma função que aloca umaFoo
estrutura opaca sem inicializá-la. Ou talvez você precise reinicializar umaFoo
estrutura que foi inicializada anteriormente com valores diferentes.fonte