Object is already initialized on declaration?

后端 未结 5 614
孤城傲影
孤城傲影 2021-02-04 08:13

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

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

SomeClass::         


        
相关标签:
5条回答
  • 2021-02-04 08:46

    What you are doing in the first line of main() is to allocate a SomeClass object on the stack. The new operator instead allocates objects on the heap, returning a pointer to the class instance. 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;. On the other hand, heap allocation is performed in C with malloc(). malloc() returns a pointer to a newly allocated space, which is then cast to a pointer-to something. 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. 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. Before the new, you have no object. In C++ this is not the case. 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). However, this is not the whole story. Check the comments for more accurate details.

    0 讨论(0)
  • 2021-02-04 08:52

    In your case, sc is allocated on the stack, using the default constructor for SomeClass. 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.)

    The new keyword, instead of allocating memory on the run-time stack, allocates the memory on the heap. 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.

    0 讨论(0)
  • 2021-02-04 08:59

    When you declare a variable (without extern) in a function scope (e.g. in main) you also defined the variable. 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.

    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.

    A new expression is used in C++ to dynamically create an object. 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, e.g. 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#.

    When an object is created with new, it must at some point be destroyed through a delete expression. 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, e.g. a shared_ptr from tr1 or boost.

    0 讨论(0)
  • 2021-02-04 09:00

    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...

    0 讨论(0)
  • 2021-02-04 09:02

    Some other answers are basically telling you "sc is allocated on the stack, new allocates the object on the heap". 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. Instead, the way I prefer to think about it is the way the C++ standard describes it:

    sc is variable of type SomeClass, declared at block scope (ie, the braces that make up the main function). This is called a local variable. Because it is not declared static or extern, this makes it have automatic storage duration. What this means is that whenever the line 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. 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. This object will be initialized when you call new, and will only be destroyed if you call delete on it. In order to call delete, you need to maintain a reference to it, and call delete when you are finished using the object. 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. 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.

    0 讨论(0)
提交回复
热议问题