小编典典

为什么 C++ 程序员应该尽量减少“新”的使用?

all

我偶然发现了 Stack Overflow 问题Memory leak with std::string when using
std::list
其中一条评论说:

停止使用new这么多。我看不出你在任何地方使用 new 的任何原因。 您可以在C++ 中按值创建对象,这是使用该语言的巨大优势之一。
您不必在堆上分配所有内容。
不要像 Java 程序员那样思考。

我不太确定他的意思是什么。

为什么要在C++ 中尽可能频繁地按值创建对象,它在内部有什么不同?
我误解了答案吗?


阅读 113

收藏
2022-02-28

共1个答案

小编典典

有两种广泛使用的内存分配技术:自动分配和动态分配。通常,每个都有一个相应的内存区域:堆栈和堆。

堆栈总是按顺序分配内存。它可以这样做是因为它要求您以相反的顺序(先进后出:FILO)释放内存。这是许多编程语言中局部变量的内存分配技术。它非常非常快,因为它需要最少的簿记并且要分配的下一个地址是隐式的。

在 C++ 中,这称为 自动存储 ,因为在范围结束时会自动声明存储。一旦当前代码块(用
分隔{})的执行完成,就会自动收集该块中所有变量的内存。这也是调用 析构函数 来清理资源的时刻。

堆允许更灵活的内存分配模式。簿记更复杂,分配更慢。因为没有隐式释放点,所以必须手动释放内存,使用deleteor delete[](free
C 中)。然而,没有隐式释放点是堆灵活性的关键。

使用动态分配的原因

即使使用堆的速度较慢并且可能导致内存泄漏或内存碎片,动态分配也有非常好的用例,因为它的限制较少。

使用动态分配的两个主要原因:

  • 您不知道编译时需要多少内存。例如,在将文本文件读入字符串时,您通常不知道文件的大小,因此在运行程序之前您无法决定分配多少内存。

  • 您想要分配在离开当前块后将持续存在的内存。例如,您可能想要编写一个string readfile(string path)返回文件内容的函数。在这种情况下,即使堆栈可以保存整个文件内容,您也无法从函数返回并保留分配的内存块。

为什么动态分配通常是不必要的

在 C 中有一个简洁的构造,称为 析构函数
。此机制允许您通过将资源的生命周期与变量的生命周期对齐来管理资源。这种技术称为RAII,是
C
的区别点。它将资源“包装”到对象中。 std::string是一个完美的例子。这个片段:

int main ( int argc, char* argv[] )
{
    std::string program(argv[0]);
}

实际上分配了可变数量的内存。对象使用std::string堆分配内存并在其析构函数中释放它。在这种情况下,您 无需
手动管理任何资源,仍然可以获得动态内存分配的好处。

特别是,这意味着在此代码段中:

int main ( int argc, char* argv[] )
{
    std::string * program = new std::string(argv[0]);  // Bad!
    delete program;
}

有不需要的动态内存分配。该程序需要更多的输入(!)并引入了忘记释放内存的风险。它这样做没有明显的好处。

为什么应该尽可能频繁地使用自动存储

基本上,最后一段总结了它。尽可能频繁地使用自动存储使您的程序:

  • 打字速度更快;
  • 运行时更快;
  • 不太容易发生内存/资源泄漏。

奖励积分

在引用的问题中,还有其他问题。特别是以下类:

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("foo_bar");
}

Line::~Line() {
    delete mString;
}

实际上使用起来比下面的风险大得多:

class Line {
public:
    Line();
    std::string mString;
};

Line::Line() {
    mString = "foo_bar";
    // note: there is a cleaner way to write this.
}

原因是std::string正确定义了一个复制构造函数。考虑以下程序:

int main ()
{
    Line l1;
    Line l2 = l1;
}

使用原始版本,该程序可能会崩溃,因为它delete在同一字符串上使用了两次。使用修改后的版本,每个Line实例都将拥有自己的字符串 实例
,每个实例都有自己的内存,并且两者都将在程序结束时释放。

其他注意事项

由于上述所有原因,广泛使用RAII被认为是 C++
中的最佳实践。
但是,还有一个不是立即显而易见的额外好处。基本上,它比各个部分的总和要好。整个机制
组成 。它可以扩展。

如果您使用Line该类作为构建块:

 class Table
 {
      Line borders[4];
 };

然后

 int main ()
 {
     Table table;
 }

分配四个std::string实例,四个Line实例,一个Table实例和所有字符串的内容, 一切都被自动释放

2022-02-28