31

単純な 'C' 構造体を初期化することを忘れないようにする代わりに、次のようにコンストラクターで派生させてゼロにすることができます。

struct MY_STRUCT
{
    int n1;
    int n2;
};

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

このトリックは、Win32 構造を初期化するためによく使用され、どこにでもあるcbSizeメンバーを設定できる場合があります。

memset 呼び出しを破棄するための仮想関数テーブルがない限り、これは安全な方法ですか?

4

16 に答える 16

84

ベースの値を初期化するだけで、そのすべてのメンバーがゼロになります。これは保証されています

struct MY_STRUCT
{
    int n1;
    int n2;
};

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

これが機能するには、例のように、基本クラスにユーザー宣言されたコンストラクターがあってはなりません。

それは嫌なことではありませんmemsetmemset実際には機能するはずですが、コードで機能することは保証されていません。

于 2009-08-22T03:15:07.087 に答える
19

前文:

私の答えはまだOKですが、次の理由から、 litbの答えは私のものよりもかなり優れていることがわかります。

  1. 知らなかった裏ワザを教えてくれる(litbさんの回答はたいていこのような効果があるのですが、書き留めたのはこれが初めてです)
  2. 質問に正確に答えます(つまり、元の構造体の部分をゼロに初期化します)

だから、私の前にlitbの答えを考えてください。実際、質問の作成者には、litb の回答を正しい回答と見なすことをお勧めします。

元の答え

真のオブジェクト (std::string) などを内部に配置すると、真のオブジェクトが memset の前に初期化され、ゼロで上書きされるため、破損します。

初期化リストの使用は g++ では機能しません (驚いています...)。代わりに、CMyStruct コンストラクター本体で初期化します。C++ フレンドリーになります。

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

PS:もちろん、MY_STRUCT を制御できないと仮定しました。コントロールがあれば、コンストラクターを MY_STRUCT 内に直接追加し、継承について忘れていたでしょう。非仮想メソッドを C のような構造体に追加しても、構造体として動作させることができることに注意してください。

編集:Lou Franco のコメントの後に、不足している括弧を追加しました。ありがとう!

編集 2: g++ でコードを試しましたが、何らかの理由で初期化リストを使用しても機能しません。body コンストラクターを使用してコードを修正しました。ただし、解決策はまだ有効です。

元のコードが変更されたため、投稿を再評価してください (詳細については、変更ログを参照してください)。

編集 3 : ロブのコメントを読んだ後、彼は議論に値するポイントを持っていると思います:「同意しますが、これは新しい SDK で変更される可能性のある巨大な Win32 構造になる可能性があるため、memset は将来の証拠です。」

私は同意しません: Microsoft を知っているので、完全な下位互換性が必要なため、Microsoft が変更されることはありません。代わりに、MY_STRUCT と同じ初期レイアウトを持つ拡張 MY_STRUCT Ex構造体を作成し、最後に追加のメンバーを持ち、RegisterWindow、IIRC に使用される構造体のような「サイズ」メンバー変数で認識できます。

したがって、ロブのコメントから残っている唯一の有効なポイントは、「巨大な」構造体です。この場合、おそらく memset の方が便利ですが、MY_STRUCT を継承するのではなく、CMyStruct の変数メンバーにする必要があります。

別のハックが見られますが、構造体のアライメントの問題が発生する可能性があるため、これは壊れると思います。

編集 4: Frank Krueger のソリューションをご覧ください。移植可能であるとは約束できませんが (おそらく移植可能だと思います)、技術的な観点から見て興味深いのは、C++ で「this」ポインタの「アドレス」が基底クラスから継承されたクラスに移動する 1 つのケースを示しているからです。 .

于 2008-09-21T20:51:02.990 に答える
12

memset よりもはるかに優れていますが、代わりにこの小さなトリックを使用できます。

MY_STRUCT foo = { 0 };

これにより、すべてのメンバーが 0 (またはデフォルト値 iirc) に初期化され、それぞれに値を指定する必要がなくなります。

于 2008-09-21T20:56:34.020 に答える
9

vtableこれにより、 (またはコンパイラが悲鳴を上げる)場合でも機能するはずなので、より安全に感じることができます。

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

あなたのソリューションはうまくいくと確信していますが、ミキシングmemsetとクラスの際に行われる保証はないと思います。

于 2008-09-21T20:52:48.647 に答える
4

これは、C のイディオムを C++ に移植する完璧な例です (そして、なぜそれが常に機能するとは限らないのか...)

memset を使用する際に発生する問題は、C++ では、構造体とクラスがまったく同じものであることです。ただし、デフォルトでは、構造体には公開の可視性があり、クラスには非公開の可視性があります。

したがって、後で、善意のプログラマーが MY_STRUCT を次のように変更した場合はどうなるでしょうか。


struct MY_STRUCT
{
    int n1;
    int n2;

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

その単一の関数を追加することで、memset が大混乱を引き起こす可能性があります。comp.lang.c+に詳細な議論があります

于 2008-09-21T21:06:43.873 に答える
3

例には「未指定の動作」があります。

非 POD の場合、コンパイラがオブジェクト (すべての基本クラスとメンバー) をレイアウトする順序は指定されていません (ISO C++ 10/3)。次の点を考慮してください。

struct A {
  int i;
};

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

private:
  int j;
};

これは次のようにレイアウトできます。

[ int i ][ int j ]

または次のように:

[ int j ][ int i ]

したがって、「this」のアドレスで memset を直接使用することは、ほとんど特定されていない動作です。上記の回答の 1 つは、一見すると安全に見えます。

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

ただし、厳密に言えば、これも不特定の動作になると思います。規範的なテキストが見つかりませんが、10/5 のメモには、「基本クラスのサブオブジェクトは、同じ型の最も派生したオブジェクトのレイアウトとは異なるレイアウト (3.7) を持つ場合があります」と書かれています。

その結果、I コンパイラーはさまざまなメンバーでスペースの最適化を実行できました。

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

次のようにレイアウトできます。

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

32 ビット システムでは、'B' のアラインメントなどにより、sizeof(B) はおそらく 8 バイトになります。ただし、コンパイラがデータ メンバーをパックする場合、sizeof(C) は '8' バイトになることもあります。したがって、memset を呼び出すと、'c1' に指定された値が上書きされる可能性があります。

于 2008-09-22T09:14:01.877 に答える
2

C++ では、クラスまたは構造体の正確なレイアウトは保証されていません。そのため、外部からそのサイズを推測するべきではありません (つまり、コンパイラではない場合)。

おそらく、動作しないコンパイラを見つけるか、いくつかの vtable をミックスに投入するまで動作します。

于 2008-09-21T20:50:52.320 に答える
1

コンストラクターが既にある場合は、n1=0; で初期化しないでください。n2=0; -- 確かに、それはより通常の方法です。

編集: 実際、paercebal が示しているように、ctor の初期化はさらに優れています。

于 2008-09-21T20:51:38.043 に答える
1

私の意見はノーです。それが何の得になるのかもわかりません。

CMyStruct の定義が変更され、メンバーを追加/削除すると、バグが発生する可能性があります。簡単に。

MyStruct にパラメーターを持つ CMyStruct のコンストラクターを作成します。

CMyStruct::CMyStruct(MyStruct &)

またはそれが求められた何か。次に、パブリックまたはプライベートの「MyStruct」メンバーを初期化できます。

于 2008-09-21T20:54:00.283 に答える
1

litbの回答にコメントしてください(直接コメントすることはまだ許可されていないようです):

この優れた C++ スタイルのソリューションを使用しても、POD 以外のメンバーを含む構造体に単純にこれを適用しないように十分注意する必要があります。

一部のコンパイラは、正しく初期化されなくなります。

同様の質問に対するこの回答を参照してください。個人的には、VC2008 で std::string を追加して悪い経験をしました。

于 2011-05-31T07:48:31.590 に答える
1

MY_STRUCT がコードで、C++ コンパイラの使用に満足している場合は、クラスにラップせずにコンストラクタを配置できます。

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

効率についてはわかりませんが、効率が必要であることを証明していないときにトリックを行うのは嫌いです。

于 2009-08-21T16:29:15.233 に答える
1

ISO C++ の観点からは、次の 2 つの問題があります。

(1) オブジェクトは POD ですか? 頭字語は Plain Old Data の略で、標準では POD に含めることができないものを列挙しています (ウィキペディアに適切な要約があります)。POD でない場合は、memset できません。

(2) オールビットゼロが無効なメンバはありますか? Windows と Unix では、NULL ポインターはすべてのビットがゼロです。そうである必要はありません。浮動小数点 0 は、非常に一般的な IEEE754 と x86 ではすべてのビットが 0 です。

Frank Kruegers のヒントは、memset を非 POD クラスの POD ベースに制限することで、あなたの懸念に対処しています。

于 2008-09-21T22:14:08.563 に答える
1

これを試してください-新しいものをオーバーロードしてください。

編集:追加する必要があります-コンストラクターが呼び出される前にメモリがゼロになるため、これは安全です。大きな欠陥 - オブジェクトが動的に割り当てられている場合にのみ機能します。

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

};
于 2008-09-21T22:25:07.163 に答える
0

ちょっとしたコードですが、再利用可能です。一度含めると、どのPODでも機能するはずです。このクラスのインスタンスは、MY_STRUCT を予期する任意の関数に渡すか、GetPointer 関数を使用して構造を変更する関数に渡すことができます。

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;

このように、NULL がすべて 0 であるか、浮動小数点表現であるかを気にする必要はありません。STR が単純な集計型である限り、問題はありません。STR が単純な集計型でない場合、コンパイル時にエラーが発生するため、誤ってこれを誤用する心配はありません。また、型にもっと複雑なものが含まれていても、デフォルトのコンストラクターがある限り問題ありません。

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

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

マイナス面としては、余分な一時コピーを作成しているため遅くなり、コンパイラは各メンバーを 1 つの memset ではなく個別に 0 に割り当てます。

于 2008-09-22T16:23:32.420 に答える
0

構造は提供されており、変更できないと思います。構造を変更できる場合、明らかな解決策はコンストラクターを追加することです。

構造を初期化する単純なマクロだけが必要な場合は、C++ ラッパーを使用してコードを過度に設計しないでください。

#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;
}
于 2009-10-26T05:44:48.630 に答える
0

私がしているのは、集約初期化を使用することですが、気になるメンバーの初期化子のみを指定します。たとえば、次のようになります。

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

残りのメンバーは、適切な型のゼロに初期化されます (Drealmer の投稿のように)。ここでは、Microsoft が途中で新しい構造体メンバーを追加することによって互換性を不当に壊すことはないと信じています (合理的な仮定)。このソリューションは最適だと思います.1つのステートメント、クラス、memset、浮動小数点ゼロまたはヌルポインターの内部表現に関する仮定はありません。

継承を伴うハックは恐ろしいスタイルだと思います。公開継承は、ほとんどの読者にとって IS-A を意味します。また、ベースとして設計されていないクラスから継承していることにも注意してください。仮想デストラクタがないため、ベースへのポインタを介して派生クラス インスタンスを削除するクライアントは、未定義の動作を呼び出します。

于 2008-09-22T19:01:50.670 に答える