对象已经在声明上初始化了? [英] Object is already initialized on declaration?

查看:82
本文介绍了对象已经在声明上初始化了?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图理解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 ++),我想知道当我需要的东西新的和什么时候释放这样的对象。我发现一个称为RAll的模式,似乎是不相关的。



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

解决方案

我认为这里有几件事:




  • 自动变量和动态分配变量之间的差异

  • 对象的生命周期

  • RAII

  • C#parallel



自动与动态



自动变量是系统将管理其寿命的变量。让我们暂时忽略全局变量,它很复杂,并且集中于通常的情况:

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

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



我们通常说一个变量的范围:从声明的点到相应的闭括号;并且语言保证在范围退出时被销毁,无论是 return 还是异常。



在你调用可怕的未定义行为,通常导致崩溃的情况下,当然不是保证。



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

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

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



在行(3) sc 被分配一个空指针值( nullptr 在C ++ 0x),因为它不指向任何实例 SomeClass 。注意,该语言不能保证任何初始化,所以你需要显式地赋值,否则你会有一个垃圾值。



在行(4)我们创建一个动态变量(使用 new 运算符)并将其地址分配给 sc 。注意,动态变量本身没有命名,系统只给我们一个指针(地址)。



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


$ b Sutter有一个非常有趣的文章。这是第一个



作为摘要:




  • 对象的构造函数运行完成后立即生效。这意味着如果构造函数抛出,对象永远不会生存(认为它是怀孕的意外事件)。

  • 一个对象在其析构函数被调用后立即死亡,如果析构函数抛出是EVIL),它不能再次尝试,因为你不能调用一个死对象的任何方法,它是未定义的行为。



第一个例子:

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

sc 从行(4)到行(5)包括在内。



RAII



RAII表示资源获取正在初始化。这是一个管理资源的习语,特别是确保资源最终会在获得后被释放。



在C ++中,由于我们没有垃圾集合,这个习语主要应用于内存管理,但也适用于任何其他类型的资源:多线程环境中的锁,文件锁,网络中的套接字/连接等...



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



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

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

它与第一个例子非常相似,除了我动态分配一个实例 SomeClass 。然后将该实例的地址传递给 std :: unique_ptr< SomeClass> 类型的 sc 对象一个C ++ 0x设施,使用 boost :: scoped_ptr 如果不可用)。 unique_ptr 保证当 sc 被销毁时,指向的对象将被销毁。



在一个更复杂的形式中,它可能使用(例如) std :: shared_ptr 耦合到多个自动变量,其名称意味着允许共享一个对象,并保证在最后一个共享者被销毁时对象将被销毁。注意,这不等同于使用垃圾回收器,并且可能存在引用循环的问题,我不会深入这里,所以只是记住 std :: shared_ptr



由于在面对异常和多线程代码时完全管理没有RAII的动态变量的生命周期是非常复杂的,因此建议: / p>


  • 尽可能多地使用自动变量

  • 对于动态变量,从不调用删除并始终使用RAII设施



我个人认为任何发生的 delete 是非常可疑的,我总是要求在代码审查中将其删除:它是一个代码气味。



C#parallel



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




  • 如果你只声明一个变量,而不赋值,它的值为null:本质上你只是操作指针,因此有一个空指针(初始化是保证的,感谢良好)

  • 使用 new 创建值,对象,并得到对象的地址;请注意语法与动态变量的C ++类似。



但是,与C ++不同,C#是垃圾回收的,所以你不必担心内存管理。



垃圾收集也意味着对象的生命周期更难以理解:它们是在你请求它们时创建的,但是在系统方便时被销毁。这可能是一个实现RAII的问题,例如,如果你真的想快速释放锁,并且语言有一些设施,帮助你使用关键字+ IDisposable 从内存接口。



* 检查,如果在声明一个变量的值为 null 之后,它将是一个动态变量。我相信对于 int 的值将是0,表示它不是,但是已经3年了,因为我搞了一个课程项目的C#...


I'm trying to understand something in C++. Basically I have this:

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

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

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

I thought that sc is an uninitialized variable of type SomeClass, but from the various tutorials I found it looks like this declaration is actually an initialization that calls the SomeClass() contructor, without me needing to call "sc = new SomeClass();" or something like that.

As I come from the C# world (and know a bit C, but no C++), I'm trying to understand when I need stuff like new and when to release objects like that. I found a pattern called RAll which seems to be unrelated.

What is this type of initialization called and how do I know if something is a mere declaration or a full initialization?

解决方案

I think there are several things here:

  • Difference between automatic variable and dynamically allocated variable
  • Lifetime of objects
  • RAII
  • C# parallel

Automatic vs Dynamic

An automatic variable is a variable of which the system will manage the lifetime. Let's ditch global variables at the moment, it's complicated, and concentrate on the usual case:

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

Here sc is an automatic variable. It is guaranteed to be fully initialized (ie the constructor is guaranteed to have run) after execution of line (3) completed successfully. Its destructor will be automatically invoked on line (6).

We generally speak of the scope of a variable: from the point of declaration to the corresponding closing bracket; and the language guarantees destruction when the scope will be exited, be it with a return or an exception.

There is of course no guarantee in the case you invoke the dreaded "Undefined Behavior" which generally results into a crash.

On the other hand, C++ also has dynamic variables, that is variables that you allocate using 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)

Here sc is still an automatic variable, however its type differ: it's now a pointer to a variable of type SomeClass.

On line (3) sc is assigned a null pointer value (nullptr in C++0x) because it doesn't point to any instance of SomeClass. Note that that the language does not guarantee any initialization on its own, so you need to explicitly assign something otherwise you'll have a garbage value.

On line (4) we build a dynamic variable (using the new operator) and assign its address to sc. Note that the dynamic variable itself is unnamed, the system only gives us a pointer (address) to it.

On line (7) the system automatically destroys sc, however it does not destroys the dynamic variable it pointed to, and thus we now have a dynamic variable whose address is not stored anywhere. Unless we are using a garbage collector (which isn't the case in standard C++), we thus have leaked memory since the variable's memory won't be reclaimed before the process ends... and even then the destructor will not be run (too bad if it had side effects).

Lifetime of Objects

Herb Sutter has a very interesting articles on this subject. Here is the first.

As a summary:

  • An object lives as soon as its constructor runs to completion. It means that if the constructor throws, the object never lived (consider it an accident of pregnancy).
  • An object is dead as soon as its destructor is invoked, if the destructor throws (this is EVIL) it cannot be attempted again because you cannot invoke any method on a dead object, it's undefined behavior.

If we go back to the first example:

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

sc is alive from line (4) to line (5) inclusive. On line (3) it's being constructed (which may fail for any number of reasons) and on line (6) it's being destructed.

RAII

RAII means Resources Acquisition Is Initialization. It's an idiom to manage resources, and notably to be sure that the resources will eventually be released once they've been acquired.

In C++, since we do not have garbage collection, this idiom is mainly applied to memory management, but it's also useful for any other kind of resources: locks in multithreaded environments, files locks, sockets / connections in network, etc...

When used for memory management, it's used to couple the lifetime of dynamic variable to the lifetime of a given set of automatic variables, ensuring that the dynamic variable will not outlive them (and be lost).

In its simplest form, it's coupled to a single automatic variable:

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

It's very similar to the first example, except that I dynamically allocate an instance of SomeClass. The address of this instance is then handed to the sc object, of type std::unique_ptr<SomeClass> (it's a C++0x facility, use boost::scoped_ptr if unavailable). unique_ptr guarantees that the object pointed to will be destroyed when sc is destroyed.

In a more complicated form, it might be coupled to several automatic variables using (for example) std::shared_ptr, which as the name implies allows to share an object and guarantees that the object will be destroyed when the last sharer is destroyed. Beware that this is not equivalent to using a garbage collector and there can be issues with cycles of references, I won't go in depth here so just remember than std::shared_ptr isn't a panacea.

Because it's very complicated to perfectly manage the lifetime of a dynamic variable without RAII in the face of exceptions and multithreaded code, the recommendation is:

  • use automatic variables as much as possible
  • for dynamic variables, never invoke delete on your own and always makes use of RAII facilities

I personally consider any occurrence of delete to be strongly suspicious, and I always ask for its removal in code reviews: it's a code smell.

C# parallel

In C# you mainly use dynamic variables*. This is why:

  • If you just declare a variable, without assignment, its value is null: in essence you are only manipulating pointers and you thus have a null pointer (initialization is guaranteed, thanks goodness)
  • You use new to create values, this invoke the constructor of your object and yields you the address of the object; note how the syntax is similar to C++ for dynamic variables

However, unlike C++, C# is garbage collected so you don't have to worry about memory management.

Being garbage collected also means that the lifetime of objects is more difficult to understand: they are built when you ask for them but destroyed at the system's convenience. This can be an issue to implement RAII, for example if you really wish to release the lock rapidly, and the language have a number of facilities to help you out using keyword + IDisposable interface from memory.

*: it's easy to check, if after declaring a variable its value is null, then it will be a dynamic variable. I believe that for int the value will be 0 indicating it's not, but it's been 3 years already since I fiddled with C# for a course project so...

这篇关于对象已经在声明上初始化了?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆