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)时,我试图理解什么时候我需要像新的东西,何时释放这样的对象.我发现一个名为RAll的模式似乎是无关的.
这是什么类型的初始化被调用,我如何知道某些东西只是一个声明或一个完整的初始化?
解决方法
>自动变量与动态分配变量的区别
>物件的寿命
> RAII
> C#并行
自动与动态
自动变量是系统管理生命周期的变量.现在我们来挖掘全球变量,这很复杂,而且集中在通常的情况:
int main(int argc,char* argv[]) // 1 { // 2 SomeClass sc; // 3 sc.foo(); // 4 return 0; // 5 } // 6
这里sc是一个自动变量.在执行线(3)成功完成后,保证完全初始化(即构造函数保证运行).它的析构函数将在第(6)行自动调用.
我们一般讲一个变量的范围:从声明的角度到相应的关闭括号;当范围退出时,语言将保证破坏,无论是退货还是异常.
当然,您无法保证您调用可怕的“Undefined Behavior”,这通常会导致崩溃.
另一方面,C也有动态变量,即使用新分配的变量.
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被分配一个空指针值(C0中为nullptr),因为它不指向SomeClass的任何实例.请注意,该语言不能自动保证任何初始化,因此您需要明确分配某些东西,否则您将有一个垃圾值.
在线(4),我们构建一个动态变量(使用新的运算符)并将其地址分配给sc.请注意,动态变量本身未命名,系统只给我们一个指针(地址).
在线(7)系统自动破坏sc,但不会破坏它所指向的动态变量,因此我们现在有一个动态变量,其地址不存储在任何地方.除非我们使用垃圾回收器(在标准C中不是这样),所以我们泄漏了内存,因为在进程结束之前变量的内存将不会被回收,甚至在析构函数不会被运行(太糟糕了,如果有副作用).
物体的寿命
Herb Sutter有一个关于这个问题的非常有趣的文章.这是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设施
我个人认为任何发生的删除是强烈怀疑的,我一直要求在代码审查中删除它:这是一个代码的气味.
C#并行
在C#中,主要使用动态变量*.这就是为什么:
>如果你只是声明一个变量,没有赋值,它的值就是null:本质上你只是操纵指针,所以你有一个空指针(初始化是保证的,谢谢你的好)
>使用new来创建值,这将调用对象的构造函数,并产生对象的地址;注意语法与动态变量的C类似
然而,与C不同,C#是垃圾收集的,所以你不必担心内存管理.
收集的垃圾也意味着物体的使用寿命更加难以理解:当您要求它们时,它们是建立在系统方便的地方被破坏的.这可能是实现RAII的一个问题,例如,如果您真的希望快速释放锁,并且该语言有许多工具来帮助您从内存中使用关键字IDisposable接口.
*:很容易检查,如果声明一个变量的值为null,那么它将是一个动态变量.我相信int的值将为0,表示不是,但是已经3年了,因为我为C#编写了一个课程,所以…