C ++对象的生命周期是什么?

时间:2013-06-15 07:36:30

标签: c++ memory-management

我是一位经验丰富的C开发人员,他刚刚进入C ++,我必须承认,我对于创建,保留和销毁C ++对象的方式非常困惑。在C中,生活很简单:在堆栈上分配=个副本,malloc / free管理堆上的数据。 C ++远非如此,或者在我看来。

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

  1. 什么是 all 创建C ++对象的方法?直接/复制构造函数,赋值等。它们如何工作?
  2. 与所有这些类型的对象创建相关的所有不同的初始化语法是什么? T f = xT f(x);T f{x};等之间的区别是什么?
  3. 最重要的是,在C ++中复制/分配/ =时是否正确,何时需要使用指针?在C中,我习惯于在 lot 周围抛出指针,因为指针赋值很便宜但是结构复制不那么容易。 C ++的复制语义如何影响这个?
  4. 最后,shared_ptrweak_ptr等所有这些内容是什么?
  5. 我很抱歉,如果这是一个有点广泛的问题,但我很困惑何时使用什么(甚至没有提到我对集合中的内存管理和new运算符的混淆),我觉得就像我所知道的关于C内存管理的一切都在C ++中崩溃了。这是真的吗,还是我的心理模型错了?

    总结一下:如何创建,初始化和销毁​​C ++对象,以及何时应该使用每种方法?

3 个答案:

答案 0 :(得分:21)

首先,你的内存管理技巧在C ++中很有用,只是它们低于C ++的做事方式,但它们就在那里......

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

  

1)创建C ++对象的所有方法是什么?

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

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

  

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

  • T f(x)是经典的方法,它只是使用以T作为参数的构造函数创建类型为x的对象。
  • 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 ++复制/分配/无论什么是正确的?何时你想使用指针?

您可以使用与C中相同的内容。将副本/作业视为memcpy。您也可以传递参考文献,但您也可以等一会儿,直到您对这些参考感到满意为止。你应该做的是:不要使用指针作为辅助局部变量,而是使用引用。

  

4)最后,所有这些东西,比如shared_ptr,weak_ptr等等?

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

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

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

答案 1 :(得分:4)

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

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 calls)视为自动存储对象!

  

最重要的是,什么时候复制/分配/无论在C ++中是否正确,以及何时使用指针?在C中,我非常习惯于在很多地方投掷指针,因为指针分配很便宜但是结构复制不那么容易。 C ++的复制语义如何影响这个?

const引用无处不在,尤其是函数参数。 const引用避免复制并阻止修改对象。如果你不能使用const ref,那么普通参考可能是合适的。如果由于某种原因您想要重置引用或将其设置为null,请使用指针。

  

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

简而言之,所有构造函数都会创建对象。作业没有。为此阅读一本书。

答案 2 :(得分:2)

  1. 除了显式对象外,C ++中隐式对象的创建方式有很多种。几乎所有这些都使用对象类的copy-constructor。 请记住:隐式复制可能要求T类型的复制构造函数和/或赋值运算符在public范围内声明,具体取决于复制的位置。
    所以当然:

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

    T object(arg);

  2. 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)使用赋值运算符创建新对象。我没有使用'copy'这个词,因为在这种情况下,特定类型的赋值运算符实现很重要。如果未实现此运算符,则默认实现由编译器创建,顺便说一下,它具有与默认复制构造函数相同的行为。

    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的书。可能是错过了什么。
    当我写这篇文章的时候,一些答案被接受了,所以我就停止了。愿我列出的细节有用。