Pergunta

Em vez de ter que se lembrar para inicializar a estrutura de um simples 'C', eu poderia tirar dela e zero-lo no construtor como este:

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        memset(this, 0, sizeof(MY_STRUCT));
    }
};

Este truque é muitas vezes usado para inicializar estruturas Win32 e, por vezes, pode definir o onipresente cbSize membro.

Agora, enquanto não houver uma tabela de função virtual para a chamada memset para destruir, isso é uma prática segura?

Foi útil?

Solução

Preâmbulo:

Enquanto a minha resposta ainda é Ok, eu encontrar resposta do litb bastante superior à minha, porque:

  1. Ele me ensina um truque que eu não sabia (respostas do litb geralmente têm esse efeito, mas esta é a primeira vez que eu anotá-la)
  2. Ele responde exatamente a questão (isto é, inicializando parte da estrutura original para zero)

Então, por favor, considere a resposta de litb antes do meu. Na verdade, eu sugiro que o autor da questão a considerar a resposta de litb como o caminho certo.

resposta Original

Colocando um verdadeiro objecto (isto é, std :: cadeia de caracteres) etc. dentro irá quebrar, porque o verdadeiro objecto serão inicializados antes do memset, e, em seguida, substituído por zeros.

Usando a lista de inicialização não funciona para g ++ (estou surpreso ...). Inicialize-o, em vez no corpo do construtor CMyStruct. Será C ++ friendly:

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct() { n1 = 0 ; n2 = 0 ; }
};

P.S .: eu achei que você tinha não controle sobre my_struct, é claro. Com o controle, você teria acrescentado o construtor diretamente dentro my_struct e esquecido herança. Note que você pode adicionar métodos não-virtuais para um C-como estrutura, e ainda ter que se comportar como um struct.

EDIT: Adicionado parêntese ausente, depois do comentário de Lou Franco. Obrigado!

EDIT 2: Eu tentei o código em g ++, e por alguma razão, usando a lista de inicialização não funciona. Eu corrigi o código usando o construtor corpo. A solução ainda é válido, no entanto.

Por favor reavaliar meu post, como o código original foi alterado (ver changelog para mais informações).

EDIT 3: Depois de ler o comentário de Rob, acho que ele tem um ponto digno de discussão: "Concordo, mas isso poderia ser uma estrutura Win32 enorme que pode mudar com um novo SDK, assim que um memset é a prova de futuro".

Eu discordo: Conhecer Microsoft, não vai mudar por causa de sua necessidade de compatibilidade com versões anteriores perfeito. Eles vão criar em vez de um my_struct estendida Ex struct com o mesmo layout inicial como my_struct, com os membros additionnal no final, e reconhecível através de um "tamanho" variável de membro como a estrutura usada para uma RegisterWindow, IIRC.

Assim, o único ponto válido restante do comentário de Rob é o "enorme" struct. Neste caso, talvez um memset é mais conveniente, mas você terá que fazer my_struct um membro variável de CMyStruct em vez de herdar a partir dele.

Eu vejo um outro corte, mas acho que isso iria quebrar por causa de possível problema de alinhamento struct.

EDIT 4: Por favor, dê uma olhada solução de Frank Krueger. Eu não posso prometer é portátil (eu acho que é), mas ainda é interessante do ponto de vista técnico, porque mostra um caso em que, em C ++, o "este" "endereço" ponteiro se desloca de sua classe base para sua classe herdada .

Outras dicas

Você pode simplesmente valor-inicializar a base, e todos os seus membros serão zero'ed fora. Isso é garantido

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct():MY_STRUCT() { }
};

Para que isso funcione, deve haver nenhum construtor declarou usuário na classe base, como no seu exemplo.

No memset desagradável para isso. Não é garantido que as obras memset no seu código, mesmo que ele deve funcionar na prática.

Muito melhor do que um memset, você pode usar este pequeno truque em vez disso:

MY_STRUCT foo = { 0 };

Isto irá inicializar todos os membros a 0 (ou sua IIRC valor padrão), não há necessidade de especificar um valor para cada um.

Isso me faz sentir muito mais seguro como deveria funcionar mesmo se há uma vtable (ou o compilador vai gritar).

memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

Estou certo de sua solução vai funcionar, mas eu duvido existem quaisquer garantias a serem feitas quando a mistura memset e classes.

Este é um exemplo perfeito de portar uma linguagem C para C ++ (e por que ele pode nem sempre trabalho ...)

O problema que você terá com o uso memset é que em C ++, uma estrutura e uma classe são exatamente a mesma coisa, exceto que, por padrão, um struct tem visibilidade pública e uma classe tem visibilidade privado.

Assim, o que se mais tarde, algum programador bem significado muda my_struct assim:


struct MY_STRUCT
{
    int n1;
    int n2;

   // Provide a default implementation...
   virtual int add() {return n1 + n2;}  
};

Ao adicionar essa única função, o seu memset pode agora causar estragos. Há uma discussão detalhada em comp.lang.c +

Os exemplos têm "comportamento não especificado".

Para um não-POD, a ordem por que o compilador estabelece um objecto (todas as bases de classes e os membros) é indeterminado (ISO C ++ 3/10). Considere o seguinte:

struct A {
  int i;
};

class B : public A {       // 'B' is not a POD
public:
  B ();

private:
  int j;
};

Isto pode ser definido como:

[ int i ][ int j ]

Ou, como:

[ int j ][ int i ]

Portanto, usando memset diretamente no endereço do 'isto' é muito comportamento não especificado. Uma das respostas acima, à primeira aparência vista ser mais seguro:

 memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));

Eu acredito, porém, que, estritamente falando isso também resulta em um comportamento não especificado. Não consigo encontrar o texto normativo, porém a nota no 10/5 diz: "Uma classe subobject base pode ter um layout (3,7) diferente do layout de um objeto mais derivado do mesmo tipo".

Como resultado, eu compilador poderia realizar otimizações espaço com os diferentes membros:

struct A {
  char c1;
};

struct B {
  char c2;
  char c3;
  char c4;
  int i;
};

class C : public A, public B
{
public:
  C () 
  :  c1 (10);
  {
    memset(static_cast<B*>(this), 0, sizeof(B));      
  }
};

Pode ser definido como:

[ char c1 ] [ char c2, char c3, char c4, int i ]

Em um sistema de 32 bits, devido a alighments etc, para 'B', sizeof (B) provavelmente será de 8 bytes. No entanto, sizeof (C) pode também ser '8' bytes se os pacotes de compilador os membros de dados. Portanto, a chamada para memset pode substituir o valor dado a 'c1'.

layout preciso de uma classe ou estrutura não é garantida em C ++, que é por isso que você não deve fazer suposições sobre o tamanho dele do lado de fora (isso significa que se você não é um compilador).

Provavelmente ele funciona, até encontrar um compilador em que isso não acontece, ou você lançar alguma vtable na mistura.

Se você já tem um construtor, porque não basta inicialize-o lá com n1 = 0; n2 = 0; -. Isso é certamente o mais normais maneira

Edit:. Na verdade, como paercebal tem mostrado, a inicialização ctor é ainda melhor

A minha opinião não é. Eu não tenho certeza do que ele ganha também.

Como a sua definição de CMyStruct muda e você adicionar / membros de exclusão, isto pode levar a erros. Facilmente.

Criar um construtor para CMyStruct que leva um MyStruct tem um parâmetro.

CMyStruct::CMyStruct(MyStruct &)

Ou algo desse procurado. Você pode, então, iniciar um público ou membro privado 'MyStruct'.

Do ponto de vista de um ISO C ++, há duas questões:

(1) é o objeto de um POD? A sigla significa Plain dados antigos e as enumera padrão que você não pode ter em um POD (Wikipedia tem um resumo bom). Se não é uma POD, você não pode memset-lo.

(2) Existem membros para o qual todos os bits de zero é inválido? No Windows e Unix, o ponteiro NULL é todos os bits zero; ele não precisa ser. Ponto flutuante 0 tem todos os bits zero em IEEE754, que é bastante comum, e em x86.

endereços ponta Frank Kruegers suas preocupações por restringir o memset à base POD da classe não-POD.

Tente isto -. Sobrecarga de novo

EDIT: devo acrescentar - Isto é seguro porque a memória é zerada antes de qualquer construtores são chamados. Grande falha -. Só funciona se o objeto é alocada dinamicamente

struct MY_STRUCT
{
    int n1;
    int n2;
};

class CMyStruct : public MY_STRUCT
{
public:
    CMyStruct()
    {
        // whatever
    }
    void* new(size_t size)
    {
        // dangerous
        return memset(malloc(size),0,size);
        // better
        if (void *p = malloc(size))
        {
            return (memset(p, 0, size));
        }
        else
        {
            throw bad_alloc();
        }
    }
    void delete(void *p, size_t size)
    {
        free(p);
    }

};

Se my_struct é o seu código, e você está feliz usando um compilador C ++, você pode colocar o construtor lá sem embrulho em uma classe:

struct MY_STRUCT
{
  int n1;
  int n2;
  MY_STRUCT(): n1(0), n2(0) {}
};

Eu não tenho certeza sobre a eficiência, mas eu odeio fazer truques quando você não provaram a eficiência é necessário.

Comment on do litb resposta (parece que eu 'm ainda não tem permissão para comentar diretamente):

Mesmo com este bom C ++ - solução de estilo que você tem que ter muito cuidado para que você não aplicar esta ingenuamente a uma estrutura que contém um membro não-POD

.

Alguns compiladores, em seguida, fazer mais não inicializar corretamente.

Veja esta resposta a uma pergunta similar. Eu, pessoalmente, teve a má experiência em VC2008 com um std :: string adicional.

O que eu faço é o uso de inicialização agregada, mas apenas initializers especificando para os membros que me interessa, por exemplo:

STARTUPINFO si = {
    sizeof si,      /*cb*/
    0,              /*lpReserved*/
    0,              /*lpDesktop*/
    "my window"     /*lpTitle*/
};

Os membros restantes serão inicializados com zeros do tipo adequado (como no post de Drealmer). Aqui, você está confiando Microsoft não compatibilidade pausa gratuitamente pela adição de novos membros da estrutura no meio (uma suposição razoável). Esta solução parece-me ideal -. Uma instrução, nenhuma classe, nenhuma memset, há suposições sobre a representação interna de ponto zero ou nulos ponteiros flutuante

Eu acho que os hacks envolvendo herança são de estilo horrível. meios de herança públicas IS-A para a maioria dos leitores. Note também que você está herdando de uma classe que não é projetado para ser uma base. Como não há nenhum processo de destruição virtual, os clientes que excluir uma instância de classe derivada através de um ponteiro para a base vai invocar um comportamento indefinido.

Eu assumo a estrutura é fornecida para você e não pode ser modificado. Se você pode mudar a estrutura, então a solução óbvia é a adição de um construtor.

Não mais de engenheiro seu código com wrappers C ++ quando tudo que você quer é uma macro simples para inicializar sua estrutura.

#include <stdio.h>

#define MY_STRUCT(x) MY_STRUCT x = {0}

struct MY_STRUCT
{
    int n1;
    int n2;
};

int main(int argc, char *argv[])
{
    MY_STRUCT(s);

    printf("n1(%d),n2(%d)\n", s.n1, s.n2);

    return 0;
}

É um pouco de código, mas é reutilizável; incluí-lo uma vez e ele deve funcionar para qualquer POD. Você pode passar uma instância desta classe para qualquer função esperando um my_struct, ou usar a função GetPointer para passá-lo em uma função que irá modificar a estrutura.

template <typename STR>
class CStructWrapper
{
private:
    STR MyStruct;

public:
    CStructWrapper() { STR temp = {}; MyStruct = temp;}
    CStructWrapper(const STR &myStruct) : MyStruct(myStruct) {}

    operator STR &() { return MyStruct; }
    operator const STR &() const { return MyStruct; }

    STR *GetPointer() { return &MyStruct; }
};

CStructWrapper<MY_STRUCT> myStruct;
CStructWrapper<ANOTHER_STRUCT> anotherStruct;

Desta forma, você não tem que se preocupar se nulos são todos 0, ou representações de ponto flutuante. Enquanto STR é um tipo agregado simples, as coisas vão funcionar. Quando STR não é um tipo agregado simples, você obterá um erro em tempo de compilação, assim você não terá que se preocupar com acidentalmente fazer mau uso dessa. Além disso, se o tipo contém algo mais complexo, enquanto ele tem um construtor padrão, você está ok:

struct MY_STRUCT2
{
    int n1;
    std::string s1;
};

CStructWrapper<MY_STRUCT2> myStruct2; // n1 is set to 0, s1 is set to "";

Em contrapartida, é mais lento desde que você está fazendo uma cópia extra temporário, eo compilador irá atribuir a cada membro para 0 individualmente, em vez de um memset.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top