【问题标题】:Replacing memset() on classes in a C++ codebase在 C++ 代码库中的类上替换 memset()
【发布时间】:2015-11-05 06:34:59
【问题描述】:

我继承了一个 C++98 代码库,它在 C++ 类上有两个主要用途 memset(),为了清晰起见扩展了宏:

 // pattern #1:
 Obj o;
 memset(&o, 0, sizeof(o));

 // pattern #2:
 // (elsewhere: Obj *o;)
 memset(something->o, 0, sizeof(*something->o));

您可能已经猜到,此代码库不使用 STL 或其他非 POD 类。当我尝试将一个 std::string 放入其中一个类时,通常会发生不好的事情。

据我了解,这些模式可以在 C++11 中重写如下:

 // pattern #1
 Obj o = {};

 // pattern #2
 something->o = {};

也就是说,在这两种情况下,{} 的赋值都会用默认初始化值重写对象的内容。漂亮又干净,不是吗?

嗯,是的,但它不起作用。它适用于 *nix 系统,但在使用带有 v120_xp 工具集的 VS2013 构建时会产生相当莫名其妙的结果(本质上是垃圾值),这意味着我对初始化列表的理解有些欠缺。

所以,问题:

  1. 为什么这不起作用?
  2. 有什么更好的方法来替换 memset 的这种使用,以确保具有构造函数的成员被正确地默认初始化,并且最好可以可靠地应用,只需搜索和替换(遗憾的是没有测试)。如果它适用于 VS2013 之前的版本,则可以加分。

【问题讨论】:

  • 是的,空大括号初始化器的工作效果不如人们想象的那么好,我遇到了许多关于成员没有完全归零的错误。不幸的是,我认为一般初始化 C++ 类的成员没有任何快速解决方法......我的理解是您必须显式初始化成员
  • Obj o = {}; 工作了吗?
  • 另外你确定你的班级是一个聚合班吗?见stackoverflow.com/questions/4178175/…
  • @personjerry Ahhh,有些类有基类——尽管甚至不是为了继承,只是为了在将代码构建为库时隐藏一些数据——事实上,其中一个已被填充在 MSVC 上有垃圾值!谢谢你的链接。

标签: c++11 object-initializers


【解决方案1】:

大括号初始化的行为取决于您尝试初始化的对象类型。

在聚合(例如简单的 C 样式结构)上,使用空的大括号初始化器对聚合进行零初始化,即它使所有成员为零。

在非聚合中,空的大括号初始化器调用默认构造函数。如果构造函数没有显式初始化成员(编译器自动生成的构造函数没有),那么成员将被构造但否则未初始化。具有自己初始化自己的构造函数的成员可以,但是例如int 成员将具有不确定的值。

IMO,解决您的问题的最佳方法是添加一个默认构造函数(如果类还没有它)和一个显式初始化成员的初始化列表。

【讨论】:

  • 未显式初始化成员但规范保证在使用大括号初始化时成员将被零初始化的非聚合:example code。但是,VC++ 在这种情况下没有提供正确的行为。
  • 我已经向非聚合类添加了这样一个默认构造函数,但是在 MSVC 上仍然有问题——我在 POD 类中的 *this = {} 上出现堆栈溢出(这个:github.com/whitequark/solvespace/blob/for-upstream/src/ui.h#L11 ),这毫无意义。
  • 原来POD类有2.6MB长,MSVC没有优化到memset的赋值。因此,在将该实例更改回显式 memset 调用后,此方法起作用了,非常感谢!
【解决方案2】:

它适用于 *nix 系统,但在使用带有 v120_xp 工具集的 VS2013 构建时会产生相当莫名其妙的结果(本质上是垃圾值),这意味着我对初始化列表的理解有些欠缺。

“默认”初始化的规则在 C++ 版本之间有所不同,但 VC++ 一直坚持 C++98 规则,我认为甚至忽略了 C++03 的更新。

其他编译器已经实施了新规则,gcc 甚至在某一时刻实施了一些尚未被官方规范接受的缺陷解决方案。

因此,即使您想要 由标准保证,但在大多数情况下,最好不要尝试依赖没有显式初始化程序的成员的初始化行为。

【讨论】:

    【解决方案3】:

    我认为展示位置new 已经足够成熟,可以在 VS 上运行,因此您可以尝试:

    #include <new>
    
    new(&o) T();
    new(something->p) T();
    

    确保不要对任何尚未分配和破坏/未初始化的对象执行此操作! (但下面指出,如果构造函数抛出异常,这可能会失败。)

    您也许可以只从默认对象分配,即o = T();*(something-&gt;p) = T();。一个好的通用策略可能是为这些 POD 类中的每一个提供一个简单的默认构造函数,其中 : o() 在初始值设定项列表中。

    【讨论】:

    • 这确实不是一个好主意。如果这些对象具有自动生命周期(如问题中所示)并且构造函数抛出,则当对象超出范围时调用的析构函数将导致未定义的行为(因为它将在已销毁的对象上调用)。
    • @Mankarse 如果 VS2013 不支持大括号初始化器,它可能也不支持 noexcept。已编辑。
    猜你喜欢
    • 2011-08-08
    • 2013-05-01
    • 2018-05-19
    • 2015-03-16
    • 2014-08-04
    • 2011-05-04
    • 2016-04-28
    • 2012-09-03
    • 1970-01-01
    相关资源
    最近更新 更多