2010-08-17 56 views
10

我想了解C++中的一些东西。基本上,我有这样的:对象在声明上已经初始化了吗?

class SomeClass { 
    public: 
     SomeClass(); 
    private: 
     int x; 
}; 

SomeClass::SomeClass(){ 
    x = 10; 
} 

int main() { 
    SomeClass sc; 
    return 0; 
} 

我以为SC是类型SomeClass的一个未初始化的变量,但是从我找到了各种教程,它看起来像这样的声明实际上是调用SomeClass的()构造器的初始化,没有我需要调用“sc = new SomeClass();”或类似的东西。当我来自C#世界(并且知道一点C,但没有C++)时,我试图了解什么时候需要像new这样的东西以及何时释放这样的对象。我发现了一种叫做RAll的模式,它似乎是不相关的。

这是什么类型的初始化调用,我怎么知道是否只是声明或完全初始化?

+6

“因为我来自C#世界”所以? C++不是C#,根本不了解C#。你需要一本[良好的初学者书](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list),你从头开始。 – GManNickG 2010-08-17 20:52:13

+0

感谢您的名单!诚然,C++和C#是两种截然不同的语言。我想我只是不想了解什么是循环或类,更多的是关于它们如何在C++中工作,因此我一直在避免书籍。我会看看清单。 – 2010-08-17 20:58:33

+0

@迈克尔:你无法避免书籍。 :) C++是它自己的语言。这听起来像你认为你已经理解了C++类,但它们与C#中的不一样。当然,你可能已经知道选择或迭代陈述了,但这只是一个章节。 – GManNickG 2010-08-17 21:00:34

回答

17

我觉得这里有几件事情:

  • 自动变量和对象的动态分配的变量
  • 终身
  • RAII
  • C#平行

自动VS之间的区别动态

自动变量是系统管理寿命的变量。让我们抛弃全局变量的那一刻,它是复杂的,并且集中在通常情况下:

int main(int argc, char* argv[]) // 1 
{         // 2 
    SomeClass sc;     // 3 
    sc.foo();      // 4 
    return 0;      // 5 
}         // 6 

这里sc是一个自动变量。在第(3)行成功完成之后,它保证被完全初始化(即构造函数保证已经运行)。它的析构函数将在第(6)行自动调用。

我们通常会谈到变量的范围:从声明点到相应的右括号;当范围退出时语言保证销毁,例如return或例外。

当然,如果您调用可能导致崩溃的可怕“未定义行为”,当然不能保证。

另一方面,C++也有动态变量,即使用new分配的变量。

int main(int argc, char* argv[]) // 1 
{         // 2 
    SomeClass* sc = 0;    // 3 
    sc = new SomeClass();   // 4 
    sc->foo();      // 5 
    return 0;      // 6 
}         // 7 (!! leak) 

这里sc仍然是一个自动可变的,但是它的类型而有所不同:它现在是一个指针,指向SomeClass类型的变量。

在线(3)sc被分配了一个空指针值(在C++ 0x中为nullptr),因为它没有指向SomeClass的任何实例。请注意,该语言不保证自己进行任何初始化,因此您需要明确指定某些内容,否则您将拥有垃圾值。

在线(4)我们建立一个动态变量(使用new算子)并将其地址分配给sc。请注意,动态变量本身是未命名的,系统只给我们一个指针(地址)给它。

在线(7)系统自动销毁sc,但它不会破坏它指向的动态变量,因此我们现在有一个动态变量,其地址不会存储在任何地方。除非我们使用垃圾收集器(这不是标准C++的情况),我们因此泄漏了内存,因为在过程结束之前变量的内存不会被回收......即使这样,析构函数也不会运行(太糟糕了,如果它有副作用)。对象

终身香草萨特对这个问题非常有趣的文章。这里是the first

作为总结:

  • 的对象,只要它的构造函数运行结束生命。这意味着如果构造函数抛出,对象永远不会存在(认为它是怀孕的意外)。
  • 一旦析构函数被调用,一个对象就会死亡,如果析构函数抛出(这是EVIL)它不能被再次尝试,因为你不能在死对象上调用任何方法,这是未定义的行为。

如果我们回到第一个例子:

int main(int argc, char* argv[]) // 1 
{         // 2 
    SomeClass sc;     // 3 
    sc.foo();      // 4 
    return 0;      // 5 
}         // 6 

sc是从线活着(4)到线(5)包容性。在线(3)正在构建(可能由于许多原因而失败)并且在线(6)正在被破坏。

RAII

RAII意味着资源获取就是初始化。这是一个管理资源的习惯用法,特别是要确保这些资源一旦被收购后最终将被释放。在C++中,由于我们没有垃圾收集,这个习惯用法主要应用于内存管理,但它也适用于任何其他类型的资源:多线程环境中的锁,文件锁,网络中的套接字/连接等...

当用于内存管理时,它用于将动态变量的生命周期与给定的一组自动变量的生命周期相耦合,确保动态变量不会超过它们(并且会丢失)。

在其最简单的形式,它耦合到单个自动变量:

int main(int argc, char* argv[]) 
{ 
    std::unique_ptr<SomeClass> sc = new SomeClass(); 
    sc->foo(); 
    return 0; 
} 

它非常类似于第一实施例,不同之处在于我动态分配的SomeClass一个实例。然后将此实例的地址交给sc对象,其类型为std::unique_ptr<SomeClass>(它是C++ 0x工具,如果不可用,则使用boost::scoped_ptr)。 unique_ptr保证当sc被销毁时指向的对象将被销毁。

在一个更复杂的形式中,它可能会耦合到几个使用(例如)std::shared_ptr的自动变量,顾名思义它允许共享一个对象并保证当最后一个共享者被销毁时该对象将被销毁。请注意,这不等同于使用垃圾回收器,并且可能存在引用循环的问题,因此我不会深入此处,因此只需记住std::shared_ptr不是万能药。

因为它是非常复杂的异常和多线程代码的脸完全管理动态变量的寿命没有RAII的建议是:

  • 使用自动变量尽可能
  • 动态变量,从来没有对自己的调用delete,总是利用了RAII设施

我个人认为的delete任何发生是强烈的怀疑,我送花儿给人要求在代码评论中删除它:这是一种代码味道。

C#并行

在C#中主要使用动态变量*。这就是为什么:

  • 如果你只需要声明一个变量,没有分配,其值为null:在本质上,你只指针进行操作,因此你有一个空指针(初始化保证,谢天谢地)
  • 您使用new来创建值,这将调用您的对象的构造函数并产生对象的地址;注意语法与动态变量的C++类似

但是,与C++不同,C#是垃圾收集的,因此您不必担心内存管理。

被垃圾收集也意味着物体的寿命更难以理解:它们是在你要求它们时建立的,但是在系统方便时被破坏。这可能是实施RAII的一个问题,例如,如果您真的希望快速释放锁,并且该语言有一些设施可以帮助您从内存中获取关键字+ IDisposable关键字+ IDisposable

*:很容易检查,如果声明一个变量后其值为null,那么它将是一个动态变量。我相信,对于int的值将为0表示它不是,但已经3年了,因为我用C#拨弄了一个课程项目,所以...

3

你在main()的第一行所做的是在堆栈上分配一个SomeClass对象。 new运算符改为在堆上分配对象,并将指针返回给类实例。这最终导致通过.两种不同的接入技术(与实例),或者使用->(与指针)

你既然知道C,执行堆栈分配每次你说,例如int i;。另一方面,使用malloc()在C中执行堆分配。 malloc()返回一个指向新分配空间的指针,然后将其转换为某个指针。例如:

int *i; 
i = (int *)malloc(sizeof(int)); 
*i=5; 

虽然在栈上分配的东西重新分配是自动完成的,的在堆上分配东西释放必须由程序员来完成。

您的困惑源于C#(我没有使用,但我知道它与Java类似)并没有堆栈分配。当你说SomeClass sc时,你要做的是宣布一个SomeClass引用,该引用当前未初始化,直到你说new,这是对象出现的时刻。在new之前,你没有任何东西。在C++中,情况并非如此。在C++中没有关于引用的概念,类似于C#(或java),尽管只有在函数调用期间才有C++引用(实际上它是一个传递引用范例)。默认情况下,C++传递值,意味着你在函数调用时复制对象)。但是,这不是整个故事。检查评论以获取更准确的细节。

+0

这里的C#/ C++比较本着正确的精神,但它们在任何一方都不是很准确。 – 2010-08-17 21:59:29

+0

@Merlyn我什么都不知道C#,很少关于Java,所以有一个不可忽略的机会,我已经不准确甚至是错误的 – 2010-08-17 22:15:47

+0

@Stefano:最近相当于C++的自动行为在C#中的值类型(结构,和许多内置类型)。它们是按照价值复制的,你不必对它们调用“新的”。如果你确实对它们调用了“new”,它们仍然被分配在它们所在的任何内存空间中(如果在本地范围内,则堆栈,如果在堆分配对象中则堆栈)。 – 2010-08-17 22:27:37

2

在你的情况下,sc被分配在堆栈上,使用SomeClass的默认构造函数。由于它在堆栈中,所以从函数返回时,实例将被破坏。 (如果您有一个从main分配给sc --the内存时会返回来未分配给main调用函数中实例化SomeClass sc这将是更令人印象深刻。)

new关键字,而不是在运行时内存分配堆栈,分配堆上的内存。由于C++没有自动垃圾收集,您(程序员)负责在堆上分配您分配的任何内存(使用关键字delete),以避免内存泄漏。

+0

谢谢。因此,如果我需要我的对象持久存在多个不相关的方法(如类成员?),我需要new/delete,而函数内的所有内容都可以进入堆栈? – 2010-08-17 21:01:08

+0

@Michael:不,你只需要:'struct foo {int bar; };'和'foo'的任何方法都可以使用'bar'。 'bar'与'foo'具有相同的生命周期,保存构造函数。 – GManNickG 2010-08-17 21:04:06

+0

@Michael:你可以在堆栈上分配,然后让子例程接受一个引用(这是一个“别名”),而不是使用指针,但这是非常不切实际的。 – 2010-08-17 21:07:46

2

当您在函数作用域中声明变量(不含extern)时(例如在main中),您还定义了该变量。变量在达到声明时出现,并在其作用域结束时(在此情况下,达到函数main的结尾)到时不存在。

当一个对象被带入时,如果它有一个用户声明的构造函数,那么它的一个构造函数将被用来初始化它。同样,如果它有一个用户声明的析构函数,当对象超出范围以在其超出范围的位置执行任何所需的清理操作时,将使用此值。这与具有可能运行或可能不运行的语言以及肯定不在确定性时间点的语言是不同的。它更像是using/IDisposable

A new表达式在C++中用于动态创建对象。它通常用于不能将对象的生命周期绑定到特定范围的地方。例如,在创建它的函数完成后它必须继续存在。它也用于编译时已知的待创建对象的确切类型,例如,在工厂功能。在Java和C#等语言中常用的动态创建对象通常可以避免。

当使用new创建对象时,它必须在某个时刻通过delete表达式销毁。为了确保程序员不会忘记这么做,通常使用某种智能指针对象来自动管理它,例如,来自tr1或boost的shared_ptr

2

其他一些答案基本上告诉你“sc分配在栈上,new分配堆上的对象”。我不想这样想,因为它将实现细节(堆栈/堆)与代码的语义混淆起来。既然你习惯了C#做事情的方式,我认为它也会造成歧义。相反,我更愿意考虑的方式是C++标准描述它的方式:

sc是在块范围(即,构成主函数的大括号)声明的SomeClass类型的变量。这被称为局部变量。因为它没有被宣布为staticextern,这使得它具有自动存储持续时间。这意味着只要执行SomeClass sc;行,变量就会被初始化(通过运行其构造函数),当变量通过退出块而超出范围时,它将被销毁(通过运行它的析构函数 - 因为你没有一个,你的对象是普通的旧数据,什么都不会完成)。

早些时候我说:“因为它没有宣布staticextern”,如果你宣布它是这样那就静态存储时间。它会在程序启动之前初始化(技术上在块范围它将在初次使用时被初始化),并在程序终止后被销毁。

使用new创建对象时,您将创建一个对象,其动态存储时间为。此对象将在您致电new时初始化,并且只有在您拨打delete时才会被销毁。为了调用删除,您需要保持对它的引用,并在完成使用该对象时调用delete。编写良好的C++代码通常不会使用这种类型的存储持续时间,相反,您通常会将值对象放入容器(例如,std::vector)中,该容器管理包含值的生存期。容器变量本身可以进入静态存储或自动存储。

希望这有助于消除一些事物的歧义,而不会堆积太多的新术语以混淆你的意思。

+2

这只是错误的。 std :: vector不管理对象的生命周期。是的,如果你将堆栈分配的成员推入它,它们被绑定到向量的范围(因为它正在复制它们),但是将堆分配的成员推入std :: vector并期望向量管理它们的生命周期是错误的,危险的建议给新来C++的人 – Falmarri 2010-08-17 23:02:14

+0

@Falmarri你错过了这一点,但我试图澄清它管理的是值类型,而不是引用类型。 – 2010-08-18 12:36:18