[英]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: 我想这里有几件事:
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: 作为总结:
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的情况下完美地管理动态变量的生命周期非常复杂,建议如下:
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: 这就是为什么:
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 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 . 因为它没有声明为
static
或extern
,所以这使它具有自动存储持续时间 。 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 . 之前我曾说过“因为它没有被声明为
static
或extern
”,如果你已经声明它,那么它将具有静态存储持续时间 。 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.