【问题标题】:What is the lifecycle of a C++ object?C++ 对象的生命周期是什么?
【发布时间】:2013-06-11 20:37:43
【问题描述】:

我是一名经验丰富的 C 开发人员,刚刚开始接触 C++,我必须承认,我很困惑有多少种方法可以创建、保留和销毁 C++ 对象。在 C 中,生活很简单:在堆栈上分配 = 副本,malloc/free 在堆上管理数据。 C++ 远非如此,至少在我看来是这样。

鉴于此,以下是我的问题:

  1. 所有有哪些方法可以创建 C++ 对象?直接/复制构造函数、赋值等。它们是如何工作的?
  2. 与所有这些类型的对象创建相关的所有不同的初始化语法是什么? T f = xT f(x);T f{x};等有什么区别?
  3. 最重要的是,何时在 C++ 中复制/分配/无论= 是正确的,您想在什么时候使用指针?在 C 语言中,我非常习惯于在 lot 周围抛出指针,因为指针分配很便宜,但结构复制却不那么重要。 C++ 的复制语义对此有何影响?
  4. 最后,shared_ptrweak_ptr 等这些都是什么?

对不起,如果这是一个有点宽泛的问题,但我很困惑何时使用什么(甚至没有提到我对集合中的内存管理和new 运算符的困惑),我觉得一切我知道 C 内存管理在 C++ 中崩溃了。这是真的,还是我的心智模型错了?

总结一下:C++ 对象是如何创建、初始化和销毁​​的,我应该什么时候使用每种方法?

【问题讨论】:

  • 你对 C++03 还是 C++11 感兴趣?
  • 你应该把它分成更小的、单个问题的帖子。
  • 我记得在我的编程语言原理课程中读到过它们
  • @juanchopanza 我明白你的意思,但我觉得所有这些问题都紧密相连。我认为拆分它会丢失我缺少的部分:整个生命周期如何组合在一起的大图。
  • 这个问题太宽泛了。读一本书。 stackoverflow.com/questions/388242/…

标签: c++ memory-management


【解决方案1】:

首先,你的内存管理技能在 C++ 中很有用,只是比 C++ 的处事方式低了一个层次,但它们是有的……

关于你的问题,它们有点宽泛,所以我会尽量简短:

1) 创建 C++ 对象的方法有哪些?

同C:它们可以是全局变量、局部自动、局部静态或动态。你可能会对构造函数感到困惑,但简单地认为每次创建对象时都会调用构造函数。总是。哪个构造函数只是创建对象时使用什么参数的问题。

Assignment 不会创建新对象,它只是从一个对象复制到另一个对象(想想memcpy,但更聪明)。

2) 与所有这些类型的对象创建相关的所有不同的初始化语法是什么? T f = x、T f(x);、T f{x};等有什么区别?

  • T f(x) 是经典的方式,它只是使用以x 作为参数的构造函数创建T 类型的对象。
  • T f{x} 是新的 C++11 统一语法,因为它可用于初始化聚合类型(数组等),但除此之外它等同于前者。
  • T f = x 这取决于 x 是否属于 T 类型。如果是,则等价于前者,如果是不同类型,则等价于T f = T(x)。这并不重要,因为允许编译器优化掉多余的副本(复制省略)。
  • T(x)。你忘了这个。一个T 类型的临时对象被创建(使用与上面相同的构造函数),它在代码中出现的任何地方都被使用,并且在当前完整表达式的末尾,它被销毁。
  • T f。这将使用默认构造函数(如果可用)创建 T 类型的值。这只是一个不带参数的构造函数。
  • T f{}。默认构造,但使用新的统一语法。请注意,T f() 不是 T 类型的对象,而是返回 T! 的函数。
  • T()。使用默认构造函数的临时对象。

3) 最重要的是,什么时候在 C++ 中复制/赋值/whatever = 是正确的,什么时候需要使用指针?

您可以使用与 C 中相同的方法。将复制/分配想象为 memcpy 的位置。您也可以传递参考,但您也可以等待一段时间,直到您对这些感到满意为止。你应该做的是:不要使用指针作为辅助局部变量,而是使用引用。

4) 最后,shared_ptr、weak_ptr 等这些东西是什么?

它们是您的 C++ 工具带中的工具。你将不得不通过经验和一些错误来学习......

  • shared_ptr 在对象的所有权共享时使用。
  • unique_ptr 在对象的所有权唯一且明确时使用。
  • weak_ptr 用于打破 shared_ptr 的树中的循环。它们不会自动检测到。
  • vector。别忘了这个!用它来创建任何东西的动态数组。

PS:你忘了问析构函数。 IMO,析构函数赋予了 C++ 个性,所以一定要大量使用它们!

【讨论】:

  • 您在 2) 中忘记了 T t;T t{}
  • @juanchopanza:行动!添加它们。
  • 太棒了。感谢您提供如此完整的答案。我知道很多这些信息都分散在 SO 周围,但这是我第一次看到一个答案,它真正清楚地列举了 all 初始化语法类型之间的差异。
  • 关于 T(x) - 它并不总是创建一个临时的。它可以是一个声明,创建一个带有自动存储的本地 x。
  • @revani: 不,= 有一个初始化,而不是一个赋值。语法相似,但行为不同。
【解决方案2】:

这是一个相当广泛的问题,但我会给你一个起点。

C 中所谓的“堆栈变量”也称为具有“自动存储”功能的对象。具有自动存储功能的对象的生命周期相当容易理解:它在控制到达其定义的点时创建,然后在超出范围时销毁:

int main() {
  int foo = 5; // creation of automatic storage
  do_stuff();
  foo = 1;

  // end of function; foo is destroyed.
}

现在,需要注意的是= 5 被认为是初始化语法的一部分,而= 1 被认为是赋值操作。我不想让您对 = 在语言语法中用于两种不同的事物感到困惑。

无论如何,C++ 将自动存储更进一步,并允许在创建和销毁该对象期间运行任意代码:构造函数和析构函数。这就产生了称为RAII 的奇妙习语,您应该尽可能使用它。使用 RAII,资源管理变得自动化。

shared_ptr、weak_ptr 等这些东西是什么?

RAII 的好例子。它们允许您将动态资源(malloc/free 调用)视为自动存储对象!

最重要的是,什么时候在 C++ 中复制/赋值/whatever = 是正确的,什么时候你想使用指针?在 C 语言中,我非常习惯于到处乱扔指针,因为指针分配很便宜,但结构复制却不那么重要。 C++ 的复制语义对此有何影响?

const 到处引用,尤其是函数参数。 const refs 避免复制并防止修改对象。如果您不能使用const ref,则很可能正常的参考是合适的。如果出于某种原因您想重置引用或将其设置为 null,请使用指针。

创建 C++ 对象的方法有哪些?直接/复制构造函数、赋值等。它们是如何工作的?

简而言之,所有构造函数都创建对象。赋值不行。为此阅读一本书。

【讨论】:

  • 这实际上非常有用且清晰,可能足以为我指明正确的方向。非常感谢!不幸的是,我打算回答这个问题并尝试其他方法,但现在我认为我做不到。我想我会让它留下来,因为它可能对其他人有用。
【解决方案3】:
  1. 除了显式方法外,在 C++ 中创建隐式对象的方法有很多。几乎所有这些都使用对象类的复制构造函数。 记住: 隐式复制可能需要在 public 范围内声明 T 类型的复制构造函数和/或赋值运算符,具体取决于复制发生的位置。
    当然:

    a) 在堆栈中显式创建一个全新的对象:

    T object(arg);

b) 显式复制现有对象:

T original(arg);
...
T copy(original);

如果T 类没有定义复制构造函数,则默认实现由编译器创建。它尝试创建传递对象的精确副本。这并不总是程序员想要的,因此自定义实现有时可能很有用。
c) 在堆中显式创建一个全新的对象:

T *ptr = new T(arg);

d) 隐式创建一个全新的对象,其构造函数只接受一个参数并且没有explicit 修饰符,例如:

class T
{
public:
    T(int x) : i(x) {}
private:
    int i;
}
...
T object = 5; // actually implicit invocation of constructor occurs here

e) 通过值传递给函数的对象的隐式复制:

void func(T input)
{
    // here `input` is a copy of an object actually passed
}
...

int main()
{
    T object(arg);
    func(object); // copy constructor of T class is invoked before the `func` is called
}

f) 按值处理异常对象的隐式复制:

void function()
{
    ...
    throw T(arg); // suppose that exception is always raised in the `function`
    ...
}
...
int main()
{
    ...
    try {
        function();
    } catch (T exception) { // copy constructor of T class is invoked here
        // handling `exception`
    }
    ...
}

g) 使用赋值运算符创建一个新对象。我没有使用“复制”这个词,因为在这种情况下,特定类型的赋值运算符实现很重要。如果这个操作符没有实现,默认实现是由编译器创建的,顺便说一句,它与默认的复制构造函数具有相同的行为。

class T
{
    T(int x) : i(x) {}
    T operator=() const
    {
        return T(*this); // in this implementation we explicitly call default copy constructor
    }
}
...
int main()
{
   ...
   T first(5);
   T second = first; // assingment operator is invoked
   ...
}

嗯,这就是我不用看 Stroustrup 的书就能记住的。可能是错过了什么。
在我写这篇文章的时候,一些答案被接受了,所以我在这一点上停下来。希望我列出的详细信息会有用。

【讨论】:

  • 您错过了 从函数返回的对象 的情况。您的 g) 点也是错误的:赋值运算符不会创建新对象,而是修改当前对象。您的示例返回 T 因此它实际上创建了一个新对象这一事实只是一个意外。
  • @rodrigo,你是对的。很早以前我曾经重载过运算符,所以我不得不在写之前修改 g) 点
猜你喜欢
  • 2014-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-11
  • 1970-01-01
  • 2011-04-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多