我的同事喜欢编写代码来使用 memset 初始化结构体的字段,如下所示:
struct Fun {
int mem;
int cpu;
std::map<int, int> cpumap;
std::string str;
Fun() {
int size = (char*)&(this->cpumap) - (char*)this;
memset(this, 0, size);
}
};
他说这段代码 100% 正确并且可以很好地完成工作。那么这是 C++ 中初始化结构体的典型方法吗?我的意思是这种代码的行为定义得很好?
最佳答案
memset
, malloc
, calloc
等等都是 C 的做事方式 - 它们不是 C++-idomatic,并且仅在 C++ 中真正受支持,因此您可以直接使用 C 代码。请注意,即使在 C 中,您也可以使用 struct someStruct = {0}
语法对结构进行零初始化,因此使用 memset
即使在 C 中,使用结构也是不必要的。memset
实际上是用于将缓冲区清零,而不是对象。
至于他关于正确性的断言,我会说他实际上是错误的。
以下是我的观察结果的详细 list :
- 需要程序员手动将表达式中的第一个和最后一个成员放入计算
size
(为什么不直接使用sizeof
运算符?)。- 我注意到您给出的示例的意图并不明确:虽然它清除了三个标量成员,但它是否也意味着清除
mem
和cpumap
成员? (如果另一个程序员添加了这两个字段并忘记更新构造函数怎么办?)
- 我注意到您给出的示例的意图并不明确:虽然它清除了三个标量成员,但它是否也意味着清除
- 在继承的情况下失败:
this
的值将指向最顶层父级的开始,而不是第一个非继承字段,因此除了盲目覆盖父级数据之外,如果父级构造函数具有相同的“初始化”逻辑,您还会多次执行此操作。 size
计算发生在运行时,而不是编译时,这会浪费 CPU 周期。- 他正在使用
int size
而不是size_t size
因此它可能不适用于sizeof(void*) != sizeof(int)
的系统(例如 x64、一些晦涩的 ISA、某些嵌入式架构等) - 他盲目地转换到
(char*)
即使这可能不合适。而sizeof(char)
保证为1
我不相信char*
有保证始终成为void*
的合适代理。- 这也是 C 风格的 Actor 阵容。在 C++ 中,强制转换运算符
static_cast
,reinterpret_cast
,和dynamic_cast
总是优先于(T)
-风格转换。
- 这也是 C 风格的 Actor 阵容。在 C++ 中,强制转换运算符
它假设所有成员都存在于其声明顺序定义的范围内。您不能在 C++ 中做出此假设(请参见此处: Do class/struct members always get created in memory in the order they were declared? ),因为 1998 和 2003 规范指出:
The order of allocation of nonstatic data members separated by an access-specifier is unspecified
因此,在这种情况下,他的代码将依赖于未定义的行为:
struct Foo { private: int a; int b; public: int c; private: int d; } Foo::Foo() { int size = (char*)&this.d - (char*)&this.a; }
危险的是,您不能假设归零成员是“有效的” -
std::map
的实现和std::string
可能有不能为零的内部成员,通过盲目删除它们,您会将它们置于未知状态。这很危险。
要点是:不要这样做。
C++ 方式是使用初始化列表,它提供了很多编译时安全性和保证,并且需要一个保证类型安全的显式初始值。语法为:
struct Foo {
someType x;
int y;
foo bar;
};
Foo:Foo() :
x(0),
y(0),
bar(some_initial_bar_value) {
// any sequential init logic goes here
}
关于c++ - 我的同事喜欢使用memset初始化struct,我怎样才能说服他不要这样做?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36243747/