我不是必须记住初始化一个简单的'C'结构,而是可以从它派生并在构造函数中将它归零:

struct MY_STRUCT
{
    int n1;
    int n2;
};

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

这个技巧通常用于初始化Win32结构,有时可以设置无处不在的 cbSize 成员。

现在,只要memset调用没有虚拟功能表来销毁,这是一种安全的做法吗?

有帮助吗?

解决方案

PREAMBLE:

虽然我的回答仍然没问题,但我发现 litb的回答比我的优越,因为:

  1. 它告诉我一个我不知道的技巧(litb的答案通常有这种效果,但这是我第一次写下来)
  2. 它完全回答了问题(即将原始struct的部分初始化为零)
  3. 所以请在我之前考虑一下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相同的初始布局,在末尾具有附加成员,并且可通过“大小”来识别。成员变量,如用于RegisterWindow,IIRC的结构。

    因此Rob的评论中剩下的唯一有效点是“巨大的”。结构。在这种情况下,也许memset更方便,但你必须使MY_STRUCT成为CMyStruct的可变成员而不是继承它。

    我看到另一个黑客,但我想这会因为可能的结构对齐问题而中断。

    编辑4:请看看Frank Krueger的解决方案。我不能保证它是可移植的(我猜它是),但从技术角度来看它仍然很有趣,因为它显示了一个案例,在C ++中,“this”和指针“地址”从其基类移动到其继承的类。

其他提示

您可以简单地初始化基数,并且其所有成员都将被清零。这是有保证的

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

通过添加单个函数,您的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是非常不明确的行为。上面的答案之一,乍一看似乎更安全:

 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'的值。

在C ++中无法保证类或结构的精确布局,这就是为什么你不应该从外部对它的大小做出假设(这意味着如果你不是编译器)。

可能它会起作用,直到你找到一个它没有的编译器,或者你把一些vtable扔进混合。

如果你已经有了一个构造函数,为什么不用n1 = 0初始化它呢? N2 = 0; - 这肯定是正常的方式。

编辑:实际上,正如paercebal所示,ctor初始化甚至更好。

我的意见不是。我不确定它会得到什么。

随着您对CMyStruct的定义发生变化并添加/删除成员,这可能会导致错误。容易。

为CMyStruct创建一个构造函数,使MyStruct具有参数。

CMyStruct::CMyStruct(MyStruct &)

或者那些寻求的东西。然后,您可以初始化公共或私人“MyStruct”成员。

从ISO C ++的角度来看,有两个问题:

(1)对象是POD吗?缩写代表Plain Old Data,标准列举了你在POD中不能拥有的东西(维基百科有一个很好的总结)。如果它不是POD,你就无法记住它。

(2)是否存在all-bits-zero无效的成员?在Windows和Unix上,NULL指针都是零位;它不一定是。浮点0在IEEE754中的所有位都为零,这很常见,并且在x86上。

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

我不确定效率,但是当你没有证明效率时,我讨厌做技巧。

评论 litb的答案(似乎我我还没有被允许直接评论:

即使使用这个漂亮的C ++风格的解决方案,你也必须非常小心,不要将它天真地应用于包含非POD成员的结构。

然后有些编译器不能正确初始化。

参见对类似问题的回答。 我个人在VC2008上遇到了额外的std :: string。

我所做的是使用聚合初始化,但只为我关心的成员指定初始值设定项,例如:

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

其余成员将被初始化为适当类型的零(如Drealmer的帖子中所示)。在这里,您相信Microsoft不会通过在中间添加新的结构成员来无偿地破坏兼容性(合理的假设)。这个解决方案让我觉得最优 - 一个语句,没有类,没有memset,没有关于浮点零或空指针的内部表示的假设。

我认为涉及继承的黑客是可怕的风格。公共继承对大多数读者来说意味着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 "";

在缺点方面,由于您正在制作额外的临时副本,因此速度较慢,编译器会将每个成员分别设置为0,而不是一个memset。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top