質問
単純な 'C' 構造体を初期化することを忘れずに、次のようにそこから派生してコンストラクター内でゼロに設定することもできます。
struct MY_STRUCT
{
int n1;
int n2;
};
class CMyStruct : public MY_STRUCT
{
public:
CMyStruct()
{
memset(this, 0, sizeof(MY_STRUCT));
}
};
このトリックは Win32 構造を初期化するためによく使用され、ユビキタスな cbサイズ メンバー。
さて、memset 呼び出しで破棄する仮想関数テーブルがない限り、これは安全な方法でしょうか?
解決
前文:
私の答えはまだOKですが、次のことがわかりました libの答え 私よりもかなり優れている理由は次のとおりです。
- 私が知らなかった裏技を教えてくれます(libbの回答にはいつもこのような効果がありますが、書き留めるのはこれが初めてです)
- これは質問に正確に答えます(つまり、元の構造体の部分をゼロに初期化します)。
したがって、私の回答の前に lib の回答を検討してください。実際、質問の作成者には、litb の答えが正しいものであると考えることをお勧めします。
元の回答
真のオブジェクトを置く(すなわち、std::string) など。真のオブジェクトは memset の前に初期化され、その後ゼロで上書きされるため、内部は壊れます。
初期化リストの使用は g++ では機能しません (驚いています...)。代わりに、CMyStruct コンストラクター本体で初期化します。C++ フレンドリーになります:
class CMyStruct : public MY_STRUCT
{
public:
CMyStruct() { n1 = 0 ; n2 = 0 ; }
};
追記:あなたが持っていると思っていました いいえ もちろん、MY_STRUCT を制御します。制御を使用すると、コンストラクターを MY_STRUCT 内に直接追加し、継承を忘れることになります。非仮想メソッドを C ライクな構造体に追加しても、構造体として動作させることができることに注意してください。
編集:ルー・フランコのコメントの後に、欠落していた括弧を追加しました。ありがとう!
編集2:g++ でコードを試してみましたが、何らかの理由で初期化リストの使用が機能しません。bodyコンストラクターを使用してコードを修正しました。ただし、この解決策はまだ有効です。
元のコードが変更されているため、私の投稿を再評価してください (詳細については変更ログを参照してください)。
編集3:ロブのコメントを読んだ後、彼は議論する価値のある論点を持っていると思います。「同意しますが、これは新しい SDK によって変更される可能性のある巨大な Win32 構造になる可能性があるため、memset は将来も保証されます。」
私は同意しません:Microsoft のことを知ると、完全な下位互換性が必要なため、それは変わりません。代わりに、拡張された MY_STRUCT が作成されます。元 MY_STRUCT と同じ初期レイアウトを持ち、末尾に追加メンバーがあり、RegisterWindow、IIRC に使用される構造体のような「サイズ」メンバー変数を通じて認識できる構造体。
したがって、ロブのコメントから残っている唯一の有効な点は、「巨大な」構造体です。この場合、おそらく memset の方が便利ですが、MY_STRUCT を継承するのではなく、CMyStruct の変数メンバーにする必要があります。
別のハックも見られますが、構造体の配置に問題がある可能性があるため、これは壊れると思います。
編集4:Frank Krueger の解決策をご覧ください。移植性があるとは保証できませんが (移植性があると思います)、C++ で「この」ポインタの「アドレス」が基本クラスから継承クラスに移動する 1 つのケースを示しているため、技術的な観点からは興味深いものです。 。
他のヒント
単純にベースの値を初期化すると、そのすべてのメンバーがゼロに設定されます。これは保証されています
struct MY_STRUCT
{
int n1;
int n2;
};
class CMyStruct : public MY_STRUCT
{
public:
CMyStruct():MY_STRUCT() { }
};
これが機能するには、例のように、基本クラスにユーザー宣言されたコンストラクターがあってはなりません。
嫌なことはありません memset
そのために。それは保証されていません memset
実際には機能するはずですが、コード内では機能します。
memset よりもはるかに優れているため、代わりに次の小さなトリックを使用できます。
MY_STRUCT foo = { 0 };
これにより、すべてのメンバーが 0 (またはデフォルト値 iirc) に初期化され、それぞれに値を指定する必要はありません。
これにより、たとえ vtable
(そうしないとコンパイラが悲鳴を上げます)。
memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));
あなたの解決策はうまくいくと確信していますが、混合するときに何か保証があるとは思えません memset
そして授業。
これは、C イディオムを C++ に移植する完璧な例です (そして、それが常に機能するとは限らない理由も...)
memset を使用する場合に発生する問題は、C++ では、デフォルトで構造体がパブリック可視性を持ち、クラスがプライベート可視性を持つことを除いて、構造体とクラスはまったく同じものであることです。
したがって、後で、善意のプログラマが MY_STRUCT を次のように変更した場合はどうなるでしょうか。
struct MY_STRUCT
{
int n1;
int n2;
// Provide a default implementation...
virtual int add() {return n1 + n2;}
};
その 1 つの関数を追加するだけで、memset が大混乱を引き起こす可能性があります。に詳細な議論があります comp.lang.c+
例には「不特定の動作」があります。
非 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」に指定された値が上書きされる可能性があります。
C++ ではクラスや構造体の正確なレイアウトは保証されていません。そのため、外部から (コンパイラでない場合は) サイズについて推測すべきではありません。
おそらく、動作しないコンパイラを見つけるか、vtable を混合物に投入するまでは動作します。
すでにコンストラクターがある場合は、そこで n1=0 で初期化してみてはいかがでしょうか。n2=0;--確かにその方が多いです 普通 方法。
編集:実際、paercebal が示したように、ctor の初期化はさらに優れています。
私の意見はノーです。それが何の得になるのかも分かりません。
CMyStruct の定義が変更され、メンバーを追加/削除すると、バグが発生する可能性があります。簡単に。
MyStruct にパラメーターを受け取る CMyStruct のコンストラクターを作成します。
CMyStruct::CMyStruct(MyStruct &)
あるいは、それを求めたもの。その後、パブリックまたはプライベートの「MyStruct」メンバーを初期化できます。
ISO C++ の観点から見ると、次の 2 つの問題があります。
(1) オブジェクトは POD ですか?この頭字語は Plain Old Data を表し、標準では POD に含めることができないものを列挙します (Wikipedia に詳しい概要があります)。POD でない場合は、memset できません。
(2) 全ビットゼロが無効なメンバはありますか?Windows および Unix では、NULL ポインタはすべてのビットが 0 です。そうである必要はありません。非常に一般的な IEEE754 および x86 では、浮動小数点 0 はすべてのビット 0 になります。
Frank Kruegers のヒントでは、memset を非 POD クラスの POD ベースに制限することで、懸念事項に対処しています。
これを試してください - 新しいオーバーロード。
編集:追加する必要があります - メモリは事前にゼロ化されるため、これは安全です どれでも コンストラクターが呼び出されます。大きな欠陥 - オブジェクトが動的に割り当てられている場合にのみ機能します。
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);
}
};
MY_STRUCT がコードであり、C++ コンパイラーを使用しても問題ない場合は、クラスでラップせずにコンストラクターをそこに置くことができます。
struct MY_STRUCT
{
int n1;
int n2;
MY_STRUCT(): n1(0), n2(0) {}
};
効率についてはわかりませんが、効率が必要であることが証明されていないときにトリックを行うのは嫌いです。
コメントする libの答え (まだ直接コメントすることは許可されていないようです):
この優れた C++ スタイルのソリューションであっても、非 POD メンバーを含む構造体にこれを単純に適用しないように十分注意する必要があります。
一部のコンパイラは正しく初期化されなくなります。
見る 同様の質問に対するこの回答。私は個人的に、追加の std::string を使用した VC2008 でひどい経験をしました。
私がやっているのは集約初期化を使用することですが、関心のあるメンバーの初期化子のみを指定します。
STARTUPINFO si = {
sizeof si, /*cb*/
0, /*lpReserved*/
0, /*lpDesktop*/
"my window" /*lpTitle*/
};
残りのメンバーは、適切な型のゼロに初期化されます (Drealmer の投稿のように)。ここでは、Microsoft が中間に新しい構造メンバーを追加することによって互換性をいたずらに破壊しないことを信頼していることになります (合理的な仮定)。このソリューションは、1 つのステートメント、クラス、memset、浮動小数点のゼロまたは null ポインターの内部表現に関する仮定を必要とせず、最適であると私には思われます。
継承を伴うハッキングはひどいスタイルだと思います。ほとんどの読者にとって、パブリック継承は IS-A を意味します。基本として設計されていないクラスから継承していることにも注意してください。仮想デストラクターがないため、base へのポインターを介して派生クラスのインスタンスを削除するクライアントは、未定義の動作を呼び出します。
構造は提供されており、変更できないと思います。構造を変更できる場合、明らかな解決策はコンストラクターを追加することです。
構造を初期化するための単純なマクロだけが必要な場合は、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;
}
ちょっとしたコードですが、再利用可能です。一度含めれば、どの 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 に割り当てることになります。