我不是必须记住初始化一个简单的'C'结构,而是可以从它派生并在构造函数中将它归零:
struct MY_STRUCT
{
int n1;
int n2;
};
class CMyStruct : public MY_STRUCT
{
public:
CMyStruct()
{
memset(this, 0, sizeof(MY_STRUCT));
}
};
这个技巧通常用于初始化Win32结构,有时可以设置无处不在的 cbSize 成员。
现在,只要memset调用没有虚拟功能表来销毁,这是一种安全的做法吗?
答案 0 :(得分:84)
您可以简单地初始化基数,并且其所有成员都将被清零。这是有保证的
struct MY_STRUCT
{
int n1;
int n2;
};
class CMyStruct : public MY_STRUCT
{
public:
CMyStruct():MY_STRUCT() { }
};
为了使其工作,基类中应该没有用户声明的构造函数,就像在您的示例中一样。
没有讨厌的memset
。我们无法保证memset
在您的代码中有效,即使它应该在实践中有效。
答案 1 :(得分:19)
虽然我的回答仍然没问题,但我发现litb's answer比我的要好得多,因为:
所以请在我之前考虑一下litb的答案。事实上,我建议问题的作者将litb的答案视为正确答案。
在内部放置一个真实对象(即std :: string)等会破坏,因为真实对象将在memset之前初始化,然后被零覆盖。
使用初始化列表对g ++不起作用(我很惊讶......)。而是在CMyStruct构造函数体中初始化它。它将是C ++友好的:
class CMyStruct : public MY_STRUCT
{
public:
CMyStruct() { n1 = 0 ; n2 = 0 ; }
};
P.S。:我当然认为你确实对MY_STRUCT有 no 控制权。使用控件,您可以直接在MY_STRUCT中添加构造函数,并忘记继承。请注意,您可以将非虚方法添加到类似C的结构中,并且仍然可以将其表现为结构。
编辑:在Lou Franco的评论之后添加了缺失的括号。谢谢!编辑2:我在g ++上尝试了代码,由于某种原因,使用初始化列表不起作用。我使用body构造函数更正了代码。不过,解决方案仍然有效。
请重新评估我的帖子,因为原始代码已更改(有关详情,请参阅更改日志)。
编辑3:在阅读Rob的评论后,我猜他有一个值得讨论的观点:“同意,但这可能是一个巨大的Win32结构,可能会随着新的SDK而改变,所以memset是未来的证明。”
我不同意:了解微软,它不会因为需要完美的向后兼容性而改变。他们将创建一个扩展的MY_STRUCT Ex 结构,其初始布局与MY_STRUCT相同,末尾有附加成员,并且可通过“size”成员变量识别,如用于RegisterWindow,IIRC的结构。
所以Rob的评论中剩下的唯一有效点是“巨大的”结构。在这种情况下,也许memset更方便,但你必须使MY_STRUCT成为CMyStruct的可变成员而不是继承它。
我看到另一个黑客,但我想这会因为可能的结构对齐问题而中断。
编辑4:请看看Frank Krueger的解决方案。我不能保证它是可移植的(我猜它是),但从技术角度来看它仍然很有趣,因为它显示了一种情况,在C ++中,“this”指针“地址”从其基类移动到其继承类
答案 2 :(得分:12)
比memset好多了,你可以用这个小技巧代替:
MY_STRUCT foo = { 0 };
这会将所有成员初始化为0(或其默认值iirc),无需为每个成员指定值。
答案 3 :(得分:9)
这会让我感觉更安全,因为即使有vtable
(或编译器会尖叫)它也应该有效。
memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));
我确信您的解决方案能够正常运行,但我怀疑在混合memset
和类时是否有任何保证。
答案 4 :(得分: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+
中有详细讨论答案 5 :(得分: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是非常不明确的行为。上面的答案之一,乍一看似乎更安全:
memset(static_cast<MY_STRUCT*>(this), 0, sizeof(MY_STRUCT));
但是,我认为,严格来说,这也导致了未指明的行为。我找不到规范性文本,但是10/5中的注释说:“基类子对象可能具有与同一类型的最派生对象的布局不同的布局(3.7)”。
因此,我编译器可以使用不同的成员执行空间优化:
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'的值。
答案 6 :(得分:2)
在C ++中无法保证类或结构的精确布局,这就是为什么你不应该从外部对它的大小做出假设(这意味着如果你不是编译器)。
可能它会起作用,直到你找到一个它没有的编译器,或者你把一些vtable扔进混合。
答案 7 :(得分:1)
如果你已经有了一个构造函数,为什么不用n1 = 0初始化它呢? N2 = 0; - 这肯定是正常的方式。
编辑:实际上,正如paercebal所示,ctor初始化甚至更好。
答案 8 :(得分:1)
我的意见不是。我不确定它会得到什么。
随着您对CMyStruct的定义发生变化并添加/删除成员,这可能会导致错误。容易。
为CMyStruct创建一个构造函数,使MyStruct具有参数。
CMyStruct::CMyStruct(MyStruct &)
或者那些寻求的东西。然后,您可以初始化公共或私人“MyStruct”成员。
答案 9 :(得分:1)
从ISO C ++的角度来看,有两个问题:
(1)对象是POD吗?缩写代表Plain Old Data,标准列举了你在POD中不能拥有的东西(维基百科有一个很好的总结)。如果它不是POD,你就无法记住它。
(2)是否存在all-bits-zero无效的成员?在Windows和Unix上,NULL指针都是零位;它不一定是。浮点0在IEEE754中的所有位都为零,这很常见,并且在x86上。
Frank Kruegers提示通过将memset限制为非POD类的POD基础来解决您的顾虑。
答案 10 :(得分: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);
}
};
答案 11 :(得分:1)
如果MY_STRUCT是您的代码,并且您对使用C ++编译器感到满意,则可以将构造函数放在那里而不包含在类中:
struct MY_STRUCT
{
int n1;
int n2;
MY_STRUCT(): n1(0), n2(0) {}
};
我不确定效率,但是当你没有证明效率时,我讨厌做技巧。
答案 12 :(得分:1)
评论litb's answer(似乎我还没有被允许直接评论):
即使使用这个漂亮的C ++风格的解决方案,你也必须非常小心,不要将它天真地应用于包含非POD成员的结构。
然后有些编译器不能正确初始化。
见this answer to a similar question。 我个人在VC2008上遇到了额外的std :: string。
答案 13 :(得分: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 "";
在缺点方面,由于您正在制作额外的临时副本,因此速度较慢,编译器会将每个成员分别设置为0,而不是一个memset。
答案 14 :(得分:0)
我所做的是使用聚合初始化,但只为我关心的成员指定初始值设定项,例如:
STARTUPINFO si = {
sizeof si, /*cb*/
0, /*lpReserved*/
0, /*lpDesktop*/
"my window" /*lpTitle*/
};
其余成员将被初始化为适当类型的零(如Drealmer的帖子中所示)。在这里,您相信Microsoft不会通过在中间添加新的结构成员来无偿地破坏兼容性(合理的假设)。这个解决方案让我觉得最优 - 一个语句,没有类,没有memset,没有关于浮点零或空指针的内部表示的假设。
我认为涉及继承的黑客是可怕的风格。公共继承对大多数读者来说意味着IS-A。另请注意,您从一个非设计为基础的类继承。由于没有虚拟析构函数,通过指向base的指针删除派生类实例的客户端将调用未定义的行为。
答案 15 :(得分: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;
}