我是一位经验丰富的C开发人员,他刚刚进入C ++,我必须承认,我对于创建,保留和销毁C ++对象的方式非常困惑。在C中,生活很简单:在堆栈上分配=
个副本,malloc
/ free
管理堆上的数据。 C ++远非如此,或者在我看来。
鉴于此,以下是我的问题:
T f = x
,T f(x);
,T f{x};
等之间的区别是什么?=
时是否正确,何时需要使用指针?在C中,我习惯于在 lot 周围抛出指针,因为指针赋值很便宜但是结构复制不那么容易。 C ++的复制语义如何影响这个?shared_ptr
,weak_ptr
等所有这些内容是什么?我很抱歉,如果这是一个有点广泛的问题,但我很困惑何时使用什么(甚至没有提到我对集合中的内存管理和new
运算符的混淆),我觉得就像我所知道的关于C内存管理的一切都在C ++中崩溃了。这是真的吗,还是我的心理模型错了?
总结一下:如何创建,初始化和销毁C ++对象,以及何时应该使用每种方法?
答案 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)
除了显式对象外,C ++中隐式对象的创建方式有很多种。几乎所有这些都使用对象类的copy-constructor。 请记住:隐式复制可能要求T
类型的复制构造函数和/或赋值运算符在public
范围内声明,具体取决于复制的位置。
所以当然:
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)使用赋值运算符创建新对象。我没有使用'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的书。可能是错过了什么。
当我写这篇文章的时候,一些答案被接受了,所以我就停止了。愿我列出的细节有用。