我一直在尝试在不需要时不初始化内存,并且正在使用malloc数组来做:
这就是我运行的:
#include <iostream>
struct test
{
int num = 3;
test() { std::cout << "Initn"; }
~test() { std::cout << "Destroyed: " << num << "n"; }
};
int main()
{
test* array = (test*)malloc(3 * sizeof(test));
for (int i = 0; i < 3; i += 1)
{
std::cout << array[i].num << "n";
array[i].num = i;
//new(array + i) i; placement new is not being used
std::cout << array[i].num << "n";
}
for (int i = 0; i < 3; i += 1)
{
(array + i)->~test();
}
free(array);
return 0;
}
输出:
0 ->- 0
0 ->- 1
0 ->- 2
Destroyed: 0
Destroyed: 1
Destroyed: 2
尽管没有构造数组索引。这是"健康"吗?也就是说,我可以简单地将破坏者视为"只是功能"吗?(除了驱动器对数据成员相对于我指定的指针的位置有隐式知识之外(
(只是指定:我不是在寻找有关C 适当使用的警告。我简单地想知道使用这种无构造方法时是否应该警惕。
(脚注:我不想使用构造函数的原因是因为很多次,内存根本不需要初始化,并且这样做很慢(
不,这是不确定的行为。对象的寿命在呼叫构造函数完成后开始,因此,如果从未调用构造函数,则该对象在技术上永远不会存在。
这种可能"似乎"在您的示例中表现正确,因为您的结构是微不足道的(int :: 〜int是一个no-op(。
您还泄漏了内存(驱动器破坏给定的对象,但是通过malloc
分配的原始内存仍然需要为free
D(。
编辑:您可能还想查看这个问题,因为这是一个非常相似的情况,只需使用堆栈分配而不是malloc
即可。这给出了围绕对象寿命和构造的标准中的一些实际报价。
我也会添加它:如果您不使用新的位置并且显然需要它(例如,struct包含一些容器类或VTable等(,您将遇到真正的麻烦。在这种情况下,省略安置新呼叫几乎可以肯定会为您带来0个非常脆弱的代码的绩效收益 - 无论哪种方式,这都是一个好主意。
是的,驱动器不过是功能。您可以随时调用它。但是,在没有匹配的构造函数的情况下称其为一个坏主意。
因此,该规则是:如果您未将内存初始化为特定类型,则可能不会解释和使用该内存作为该类型的对象;否则是未定义的行为。(用char
和unsigned char
作为例外(。
让我们通过对您的代码进行行分析进行行。
test* array = (test*)malloc(3 * sizeof(test));
此行使用系统提供的内存地址初始化指针标量array
。请注意,对于任何类型的类型,内存>不初始化。这意味着您不应将这些内存视为任何对象(即使像int
这样的标量,请撇开您的test
类类型(。
后来,您写道:
std::cout << array[i].num << "n";
这将内存作为test
类型,违反了上述规则,导致了不确定的行为。
及以后:
(array + i)->~test();
您再次使用了test
类型的内存!调用驱动器也使用对象!这也是ub。
在您的情况下,您很幸运,没有任何有害发生,并且您会得到合理的事情。但是,瑞银完全取决于您的编译器的实施。它甚至可以决定格式化您的磁盘,并且仍然是标准配合的。
也就是说,我可以简单地将驱动器视为"只是功能"?
否。尽管它在许多方面都像其他功能一样,但灾难有一些特殊功能。这些归结为类似于手动内存管理的模式。就像内存分配和交易需要成对的一样,建筑和破坏也是如此。如果跳过一个,请跳过另一个。如果您致电一个,请致电另一个。如果您坚持手动内存管理,那么施工和破坏的工具将是新的,并且明确地称为灾难。(使用new
和delete
的代码将分配和构造合并为一个步骤,而破坏和交易合并组合到另一个步骤中。(
不要跳过将要使用的对象的构造函数。这是不确定的行为。此外,构造函数越少,如果您跳过它,就越有可能出错。也就是说,随着您节省更多的时间,您会打破更多。跳过使用的对象的构造函数并不是提高效率&mdash的一种方式。这是编写破码的方法。效率低下,正确的代码胜过无效的高效代码。
一点点沮丧:这种低级管理可能会成为时间的巨大投资。只有在绩效回报的现实机会时才走这条路线。不要仅仅为了优化而将代码复杂化。还要考虑更简单的替代方案,这些替代方案可能会获得类似的结果,而较少的代码开销。也许除了以某种方式标记未初始化的对象外,没有执行初始化的构造函数?(细节和可行性取决于所涉及的类,因此扩展了这个问题的范围。(
一点点鼓励:如果您考虑标准库,则应该意识到自己的目标是可以实现的。我将提出vector::reserve
作为可以在不初始化内存的情况下分配内存的示例。
您当前在从不存在的对象访问字段时,您当前拥有UB。
您可以通过执行构造函数NOOP来使字段非初始化。然后,编译器可能很容易进行初始化,例如:
struct test
{
int num; // no = 3
test() { std::cout << "Initn"; } // num not initalized
~test() { std::cout << "Destroyed: " << num << "n"; }
};
演示
对于可读性,您可能应该将其包装在专门的类中,例如:
struct uninitialized_tag {};
struct uninitializable_int
{
uninitializable_int(uninitialized_tag) {} // No initalization
uninitializable_int(int num) : num(num) {}
int num;
};
demo