C++:在实例化类成员时使用new关键字和不使用的区别是什么?

3

在一个编程作业中,我们被给定一个模板类,其中声明了两个成员不是指针,而是实际的对象:

Foo member;

在构造函数中,我最初尝试使用member = *(new Foo());,但是发现它有时会复制新的Foo对象,从而导致内存泄漏。
最终我找到了member = Foo(),然后查阅了它们之间的区别。我了解到成员将分配在堆栈上,而不是堆上,并且一旦超出范围,它将被删除。但对于对象来说,这是如何工作的呢?
成员只有在父类/对象被删除时才会被删除吗?
我还有另一个关于member = *(new Foo());的问题。我正在初始化两个相同类型的成员变量:
// Members
Foo member1;
Foo member2;

// Constructor {
    member1 = *(new Foo());
    member2 = *(new Foo());
}

由于某些原因,似乎member1没有被复制,它保留了与初始Foo相同的地址(即在删除时没有内存泄漏)。然而,member2会被复制并具有不同的地址,导致内存泄漏。这是为什么呢?


3
如果你还没有一本好的C++入门书,那么最好去获取一本。C++对象模型与大多数其他常用编程语言完全不同,如果你要写C++代码,理解其基础知识非常重要。 - undefined
我不知道所谓的“完全不同”,但肯定有一些基本的东西被忽略了。 - undefined
@James McNellis:我想听听您如何将其归类为根本不同。您真正可以进行比较的唯一对象模型是Java,因为其他常用语言很可能是脚本语言。即便如此,指针和内存分配的基本原则在所有语言之间可能是相同的,只是实现方式不同而已。然而,它们的释放和取消引用方式可能会有所不同。 - undefined
2
@vol7ron:关于这个主题可以写一本书(我相信肯定有人已经写了……)。显然,C++对象模型与Java和.NET对象模型根本不同:生命周期是确定的,模型是以值为中心而不是引用为中心的。C++对象模型与C中的对象模型也根本不同:C没有构造或析构的概念,所有的复制和赋值实际上都是原始内存复制。仍然有可能考虑脚本语言,其中大多数像Java和.NET一样具有非确定性的对象生命周期。 - undefined
@James:我已经有一段时间没有用C/C++编程了,所以谢谢你没有把我的问题当作粗鲁的。我不确定为什么问题中涉及到了C,因为我记得它缺少class对象,并且需要大量依赖于structs,但我们在讨论C++。我认为C++/Java中对象创建的核心基础是相同的,我不确定你所说的生命周期是“确定性”的含义。不过,我认为你在评论中所说的一个是以值为中心,另一个是以引用为中心,已经准确地表达了需要说的内容。 - undefined
@vol7ron:不,你的问题一点也不显得粗鲁!(我的回答可能有点轻率,抱歉。)关于确定性:在C++程序中,你总是可以准确地知道对象何时被创建和销毁:有明确定义的规则规定了构造函数和析构函数的调用时机。因此,对象的生命周期是确定性的。在Java和C#中,你知道对象何时被创建,但不知道何时被销毁。对象在你使用完毕后由垃圾收集器在某个时间点进行销毁。因此,生命周期是非确定性的。 - undefined
2个回答

5

你的分析是不正确的。这两种情况都是内存泄漏。你所做的是分配一个对象的新副本,将值分配给成员,然后丢弃指向已分配内存的指针而不释放该内存。在这两种情况下都存在内存泄漏。

现在,考虑以下代码:

class MemberType {
  public:
    MemberType() { std::cout << "Default constructor" << std::endl; }
    MemberType(int) { std::cout << "Int constructor" << std::endl; }
};

class Example1 {
  public:
     Example1() {}
  private:
     MemberType member_;
};

class Example2 {
  public:
      Example2() : member_(5) {}
  private:
      MemberType member_;
};

int main(int argc, char** argv) {
   Example1 example1;
   Example2 example2;
   return 0;
}

这段代码将打印出不同类型的构造函数。请注意,在默认情况下成员变量会以默认方式初始化,无需任何初始化代码,因此在默认初始化情况下,您的new语句(甚至是没有new的赋值语句)都是不必要的。当使用除默认构造函数以外的构造函数来初始化成员变量时,正确的方法是使用初始化列表。在示例中,“:member_(5)”就是使用初始化列表进行初始化。更多有关C++中构造对象的信息,请参见C++ FAQ on constructors

2
member = *(new Foo());

new Foo() 动态分配了一个 Foo 对象并返回该对象的指针。通过使用 * 解引用该指针,您可以获得 Foo 对象。然后将这个对象赋值给 member,这涉及到调用 Foo 的复制赋值运算符。默认情况下,此运算符将右侧(*(new Foo()))对象的每个成员的值分配给左侧对象(member)。

问题在于:new Foo() 动态分配了一个 Foo 对象,而该对象直到您 deletenew 返回的指针之前都不会被销毁。您没有将该指针保存在任何地方,因此泄漏了动态分配的对象。

这不是初始化对象的正确方法。

member = Foo();

这将创建一个临时的、初始化的Foo对象,并将该对象赋值给member。在语句结束时(实际上是在;处),临时对象被销毁。member没有被销毁,且临时Foo对象的内容被复制到了member中,因此这正是您想要做的事情。
请注意,初始化成员变量的首选方式是使用初始化列表:
struct C {
    Foo member1, member2;

    C() : member1(), member2() { }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接