简体   繁体   English

对象已在声明中初始化?

[英]Object is already initialized on declaration?

I'm trying to understand something in C++. 我试图用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();" 我认为sc是SomeClass类型的未初始化变量,但是从各种教程我发现看起来这个声明实际上是一个调用SomeClass()构造函数的初始化,而我不需要调用“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. 当我来自C#世界(并且知道一点C,但没有C ++)时,我试图理解何时需要像new这样的东西以及什么时候发布这样的对象。 I found a pattern called RAll which seems to be unrelated. 我发现了一种名为RAll的模式似乎与此无关。

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 RAII
  • C# parallel C#并行

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. 这里sc是一个自动变量。 It is guaranteed to be fully initialized (ie the constructor is guaranteed to have run) after execution of line (3) completed successfully. 在执行第(3)行成功完成后,保证完全初始化(即构造函数保证已运行)。 Its destructor will be automatically invoked on line (6). 它的析构函数将在第(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. 当范围退出时,语言保证破坏,无论是return还是例外。

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 . 另一方面,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)

Here sc is still an automatic variable, however its type differ: it's now a pointer to a variable of type SomeClass . 这里sc仍然是一个自动变量,但它的类型不同:它现在是一个指向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 . 在第(3)行, sc被赋予一个空指针值(C ++ 0x中的nullptr ),因为它没有指向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 . 在第(4)行,我们构建一个动态变量(使用new运算符)并将其地址分配给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. 在第(7)行,系统自动销毁sc ,但它不会破坏它所指向的动态变量,因此我们现在有一个动态变量,其地址不存储在任何地方。 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). 除非我们使用垃圾收集器(在标准C ++中不是这种情况),否则我们泄漏了内存,因为变量的内存在进程结束之前不会被回收...甚至那时析构函数也不会被运行(如果它有副作用太糟糕了)。

Lifetime of Objects 物体的生命周期

Herb Sutter has a very interesting articles on this subject. Herb Sutter有一篇关于这个主题的非常有趣的文章。 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. 一旦对象被调用它就会死掉,如果析构函数抛出(这是EVIL)它就不能再次尝试,因为你不能在死对象上调用任何方法,它是未定义的行为。

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. sc从第(4)行到第(5)行(包括第5行)存活。 On line (3) it's being constructed (which may fail for any number of reasons) and on line (6) it's being destructed. 在第(3)行,它正在构建(可能由于多种原因而失败),在第(6)行它正在被破坏。

RAII RAII

RAII means Resources Acquisition Is Initialization . RAII意味着资源获取是初始化 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... 在C ++中,由于我们没有垃圾收集,这个习惯用法主要应用于内存管理,但它对任何其他类型的资源也很有用:多线程环境中的锁,文件锁,网络中的套接字/连接等等......

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 . 它与第一个示例非常相似,只是我动态分配了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). 然后将此实例的地址传递给sc对象,类型为std::unique_ptr<SomeClass> (它是一个C ++ 0x工具,如果不可用则使用boost::scoped_ptr )。 unique_ptr guarantees that the object pointed to will be destroyed when sc is destroyed. unique_ptr保证当sc被销毁时,指向的对象将被销毁。

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. 在一个更复杂的形式中,它可能使用(例如) std::shared_ptr耦合到几个自动变量,顾名思义它允许共享一个对象并保证在销毁最后一个共享器时该对象将被销毁。 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. 请注意,这不等同于使用垃圾收集器,并且可能存在引用循环的问题,我不会深入此处,所以请记住,而不是std::shared_ptr不是灵丹妙药。

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: 因为面对异常和多线程代码,在没有RAII的情况下完美地管理动态变量的生命周期非常复杂,建议如下:

  • use automatic variables as much as possible 尽可能使用自动变量
  • for dynamic variables, never invoke delete on your own and always makes use of RAII facilities 对于动态变量,永远不要自己调用delete并始终使用RAII工具

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. 我个人认为任何delete都是强烈可疑的,我总是要求在代码审查中将其删除:这是一种代码味道。

C# parallel C#并行

In C# you mainly use dynamic variables * . 在C#中,您主要使用动态变量* 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) 如果你只是声明一个没有赋值的变量,它的值是null:实质上你只是操作指针,因此你有一个空指针(保证初始化,谢天谢地)
  • You use new to create values, this invoke the constructor of your object and yields you the address of the object; 您使用new来创建值,这会调用对象的构造函数并生成对象的地址; note how the syntax is similar to C++ for dynamic variables 请注意语法与动态变量的C ++类似

However, unlike C++, C# is garbage collected so you don't have to worry about memory management. 但是,与C ++不同,C#是垃圾收集的,因此您不必担心内存管理。

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. 这可能是实现RAII的问题,例如,如果您真的希望快速释放锁,并且该语言有许多工具可以帮助您从内存中using关键字+ IDisposable接口。

* : it's easy to check, if after declaring a variable its value is null , then it will be a dynamic variable. * :它很容易检查,如果在声明变量后它的值为null ,那么它将是一个动态变量。 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... 我相信对于int该值将为0表示它不是,但是自从我为一个课程项目摆弄C#以来已经有3年了...

What you are doing in the first line of main() is to allocate a SomeClass object on the stack. 你在main()的第一行做的是在堆栈上分配一个SomeClass对象。 The new operator instead allocates objects on the heap, returning a pointer to the class instance. new运算符改为在堆上分配对象,返回指向类实例的指针 This eventually leads to the two different access techniques via the . 这最终导致了两种不同的访问技术. (with the instance) or with the -> (with the pointer) (使用实例)或使用-> (使用指针)

Since you know C, you perform stack allocation every time you say, for example int i; 既然你知道C,那么每次你说时你都会执行堆栈分配,例如int i; . On the other hand, heap allocation is performed in C with malloc(). 另一方面,堆分配在C中使用malloc()执行。 malloc() returns a pointer to a newly allocated space, which is then cast to a pointer-to something. malloc()返回一个指向新分配空间的指针,然后将其转换为指向某个东西的指针。 example: 例:

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

While deallocation of allocated stuff on the stack is done automatically, deallocation of stuff allocated on the heap must be done by the programmer. 虽然在堆栈上分配的东西的释放是自动完成的,但是在堆上分配的东西的解除分配必须由程序员完成。

The source of your confusion comes from the fact that C# (which I don't use, but I know it is similar to Java) does not have stack allocation. 您混淆的原因来自于C#(我不使用,但我知道它与Java类似)没有堆栈分配。 What you do when you say SomeClass sc , is to declare a SomeClass reference which is currently uninitialized until you say new , which is the moment when the object springs into existence. 当你说SomeClass sc ,你所做的就是声明一个SomeClass引用,它当前是未初始化的,直到你说new ,这是对象弹出的时刻。 Before the new , you have no object. new之前,你没有对象。 In C++ this is not the case. 在C ++中,情况并非如此。 There's no concept of references in C++ that is similar to C# (or java), although you have references in C++ only during function calls (it's a pass-by-reference paradigm, in practice. By default C++ passes by value, meaning that you copy objects at function call). C ++中没有类似于C#(或java)的引用概念,尽管在函数调用期间只有C ++中的引用(在实践中它是一个传递引用的范例。默认情况下,C ++按值传递,意味着你在函数调用时复制对象)。 However, this is not the whole story. 然而,这不是全部。 Check the comments for more accurate details. 查看评论以获取更准确的详细信息。

In your case, sc is allocated on the stack, using the default constructor for SomeClass . 在您的情况下,使用SomeClass的默认构造函数在堆栈上分配sc Since it is on the stack, the instance will be destructed upon returning from the function. 由于它在堆栈上,因此从函数返回时将破坏实例。 (This would be more impressive if you instantiated SomeClass sc within a function called from main --the memory allocated for sc would be unallocated upon the return to main .) (如果你在一个从main调用的函数中实例化SomeClass sc将会更令人印象深刻 - 分配给sc内存在返回main时将被取消分配。)

The new keyword, instead of allocating memory on the run-time stack, allocates the memory on the heap. new关键字不是在运行时堆栈上分配内存,而是在堆上分配内存。 Since C++ has no automatic garbage collection, you (the programmer) are responsible for unallocating any memory you allocate on the heap (using the delete keyword), in order to avoid memory leaks. 由于C ++没有自动垃圾收集,因此您(程序员)负责取消分配您在堆上分配的任何内存(使用delete关键字),以避免内存泄漏。

When you declare a variable (without extern ) in a function scope (eg in main ) you also defined the variable. 当您在函数范围(例如main )中声明变量(不带extern )时,您还定义了变量。 The variable comes into existence at the point at which the declaration is reached and goes out of existence when the end of its scope (in this case the end of the function main ) is reached. 该变量在到达声明时出现,并在其范围结束时(在本例中为函数main的结尾)到达时不存在。

When an object is brought into existence, if it has a user-declared constructor then one of its constructors is used to initialize it. 当一个对象存在时,如果它有一个用户声明的构造函数,那么它的一个构造函数用于初始化它。 Similary, if it has a user-declared destructor, this is used when the object goes out of scope to perform any required clean up actions at the point at which it goes out of scope . 类似地,如果它具有用户声明的析构函数,则当对象超出范围以在其超出范围时执行任何所需的清理操作时使用此方法 This is different from languages that have finalizers that may or may not run and certainly not at a deterministic point of time. 这与具有可能会或可能不会运行的终结器的语言不同,当然也不是在确定的时间点。 It is more like using / IDisposable . 它更像是using / IDisposable

A new expression is used in C++ to dynamically create an object. 在C ++中使用new表达式来动态创建对象。 It is usually used where the life time of the object cannot be bound to a particular scope. 它通常用于对象的生命周期不能绑定到特定范围的地方。 For example, when it must continue to exist after the function that creates it completes. 例如,在创建它的函数完成后它必须继续存在。 It is also used where the exact type of the object to be created is now known at compiler time, eg in a factory function. 它也用于现在在编译器时知道要创建的对象的确切类型的地方,例如在工厂函数中。 Dynamically create objects can often be avoided in many instances where they are commonly used in languages such as Java and C#. 在许多通常用于Java和C#等语言的情况下,通常可以避免动态创建对象。

When an object is created with new , it must at some point be destroyed through a delete expression. 使用new创建对象时,必须在某个时刻通过delete表达式销毁它。 To make sure that programmers don't forget to do this it is common to employ some sort of smart pointer object to manage this automatically, eg a shared_ptr from tr1 or boost. 为了确保程序员不要忘记这样做,通常使用某种智能指针对象来自动管理它,例如来自tr1或boost的shared_ptr

Some other answers are basically telling you "sc is allocated on the stack, new allocates the object on the heap". 其他一些答案基本上告诉你“sc在堆栈上分配,new在堆上分配对象”。 I prefer not to think about it in this way as it conflates implementation details (stack/heap) with the semantics of the code. 我不想以这种方式考虑它,因为它将实现细节(堆栈/堆)与代码的语义混为一谈。 Since you are used to the way C# does things I think it also creates ambiguities. 既然你习惯了C#做事的方式,我认为它也会产生歧义。 Instead, the way I prefer to think about it is the way the C++ standard describes it: 相反,我更喜欢考虑它的方式是C ++标准描述它的方式:

sc is variable of type SomeClass, declared at block scope (ie, the braces that make up the main function). sc是SomeClass类型的变量,在块作用域中声明(即构成主函数的大括号)。 This is called a local variable . 这称为局部变量 Because it is not declared static or extern , this makes it have automatic storage duration . 因为它没有声明为staticextern ,所以这使它具有自动存储持续时间 What this means is that whenever the line SomeClass sc; 这意味着每当SomeClass sc;SomeClass sc; is executed, the variable will be initialized (by running its constructor), and when the variable goes out of scope by exiting the block, it will be destroyed (by running its destructor - since you do not have one and your object is plain old data, nothing will be done). 执行时,变量将被初始化(通过运行其构造函数),当变量超出范围时退出块,它将被销毁(通过运行它的析构函数 - 因为你没有它,你的对象很简单数据,什么都不会做)。

Earlier I said "Because it is not declared static or extern ", if you had declared it as such it would have static storage duration . 之前我曾说过“因为它没有被声明为staticextern ”,如果你已经声明它,那么它将具有静态存储持续时间 It would be initialized before program startup (technically at block scope it would be initialized at first use), and destroyed after program termination. 它将在程序启动之前初始化(技术上在块范围,它将在第一次使用时初始化),并在程序终止后销毁。

When using new to create an object, you create an object with dynamic storage duration . 使用new创建对象时,可以创建具有动态存储持续时间的对象。 This object will be initialized when you call new , and will only be destroyed if you call delete on it. 当您调用new ,将初始化此对象,并且只有在您调用delete时才会销毁该对象。 In order to call delete, you need to maintain a reference to it, and call delete when you are finished using the object. 要调用delete,您需要维护对它的引用,并在完成对象使用后调用delete。 Well written C++ code typically does not use this type of storage duration very much, instead you will typically place value objects into containers (eg. std::vector ), which manage the lifetime of contained values. 编写良好的C ++代码通常不会非常使用这种类型的存储持续时间,而是通常将值对象放入容器(例如std::vector )中,这些容器管理包含值的生命周期。 The container variable itself can go in static storage or automatic storage. 容器变量本身可以进入静态存储或自动存储。

Hope this helps disambiguate things a little, without piling on too many new terms so as to confuse you. 希望这有助于消除歧义,而不会滥用太多新术语来混淆你。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM