对象已在声明中初始化?

Mic*_*tum 11 c++

我试图用C++来理解一些东西.基本上我有这个:

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

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

int main() {
    SomeClass sc;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我认为sc是SomeClass类型的未初始化变量,但是从各种教程我发现看起来这个声明实际上是一个调用SomeClass()构造函数的初始化,而我不需要调用"sc = new SomeClass();" 或类似的东西.

当我来自C#世界(并且知道一点C,但没有C++)时,我试图理解何时需要像new这样的东西以及什么时候发布这样的对象.我发现了一种名为RAll的模式似乎与此无关.

什么是这种类型的初始化调用,我怎么知道某些东西是仅仅是声明还是完全初始化?

Mat*_* M. 19

我想这里有几件事:

  • 自动变量和动态分配变量之间的区别
  • 物体的寿命
  • RAII
  • C#并行

自动与动态

自动变量是系统将管理生命周期的变量.让我们暂时抛弃全局变量,它很复杂,并专注于通常情况:

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

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)
Run Code Online (Sandbox Code Playgroud)

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

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

在第(4)行,我们构建一个动态变量(使用new运算符)并将其地址分配给sc.请注意,动态变量本身是未命名的,系统只给我们一个指针(地址).

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

物体的生命周期

Herb Sutter有一篇关于这个主题的非常有趣的文章.这是第一个.

作为总结:

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

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

它与第一个示例非常相似,只是我动态分配了一个实例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的一个问题,例如,如果您真的希望快速释放锁定,并且该语言有许多工具可以帮助您从内存中获取using关键字+ IDisposable接口.

*:它很容易检查,如果在声明变量后它的值是null,那么它将是一个动态变量.我相信,因为int价值将是0,表明它不是,但是自从我为一个课程项目摆弄C#以来已经有3年了...