说我有一个像这样的简单类:
class MyObj
{
char* myPtr;
public:
MyObj()
{
myPtr = malloc(30);
}
~MyObj()
{
free(myPtr);
}
}
class TestObject
{
MyObj _myObj;
public:
TestObject(MyObj myObj)
{
_myObj = myObj;
}
};
这会泄漏内存吗?我的理由是,在构造函数运行时,TestObject中已经包含了一个MyObj实例,所以在释放内存之前不会吹掉myPtr吗?分配给本地对象是否会调用要替换的对象的析构函数?如果在构造函数中直接赋值,编译器是否会优化对象实例变量的赋值?我来自C#,只是通过声明一个引用类型变量来自动初始化一个对象,所以这让我很困惑。
谢谢!
答案 0 :(得分:3)
这会泄漏内存吗?
是的。 myObj
的赋值将调用默认的复制赋值运算符,因为您没有提供覆盖。因此,将执行逐个成员的副本,并使用分配源中的myPtr
实例覆盖分配目标的myPtr
实例。引入了两个问题,这些问题在违反Rule of Three/Five/Zero的一个或多个部分时经常遇到:
myPtr
内容。因此,该指针唯一引用的原始内存被泄露。myPtr
成员中共享相同的指针值:源和分配操作的目标。后者尤其令人不安,因为myObj
在TestObject
构造函数中完成赋值后立即离开作用域。这样做,myObj
将被销毁,并由此释放myPtr
myObj
。此外,MyObj
已通过值 传递 ,而不是引用,因此隐式副本可能已经发生(由于右值移动语义)。因此,三个 myPtr
对象可能正在提升所有引用相同内存的free
,并且只要一个人释放它,其余的就会在不知不觉中吊起悬空指针。对这些指针的任何解除引用或std::vector<char>
将调用未定义的行为。
分配给本地对象是否会调用要替换的对象的析构函数?
只能调用析构函数来生活他们的同名。即,只有当对象被销毁时才会调用它们(尽管手动调用析构函数来放置新语义)。除非引入临时代码,否则复制分配不会这样做,而且代码中的情况并非如此。
如果对象实例变量在构造函数中直接赋值,编译器是否会优化它的分配?
不,但member initialization list可以在这方面提供帮助。
现代C ++编程技术经常使用RAII来完成您似乎尝试的各种方式,具体取决于您真正想要实现的目标。
每个实例的唯一数据
如果目标是每个实例的唯一动态数据,则可以使用std::string
或简单地class MyObj
{
std::vector<char> myData;
public:
MyObj() : myData(30)
{
}
}
class TestObject
{
MyObj _myObj;
public:
TestObject(MyObj myObj)
: _myObj(std::move(myObj))
{
}
};
轻松完成此操作,具体取决于基本需求。两者都是RAII数据类型,通常足以满足动态内存管理需求。
MyObj
这消除了对TestObject
中的析构函数的需要,并利用移动语义以及MyObj
构造函数中的上述成员初始化列表。 char
的所有实例都会提升MyObj
的不同向量。 TestObject
和class MyObj
{
std::shared_ptr<char> myPtr;
public:
MyObj() : myPtr(new char[30])
{
}
};
class TestObject
{
MyObj _myObj;
public:
TestObject(MyObj myObj)
: _myObj(std::move(myObj))
{
}
};
的所有分配操作都适用于默认实现。
作业共享内存
不太可能你想要这个,但它不是那么可行:
myPtr
类似的代码,但不同的成员类型。现在char
是myPtr
数组的shared pointer。对不同new
的任何分配都会加入共享列表。简而言之,赋值意味着对象引用相同的数据,引用计数确保最后的人员扫描混乱。
注意:使用这样的共享指针可能会发生内存泄漏,因为select text,date_created from item_comment where MONTH( date_created ) = 12 order by rand() limit 1000
可能会成功,但共享指针的共享数据块可能会抛出异常。这在C ++ 17中得到解决,
其中std::make_shared
支持数组分配
这些只是做一些你可能想要完成的事情的方法。我们建议您在提供的链接和本网站上阅读Rule of Three/Five/Zero和RAII概念。有很多例子可能会回答你可能有的其他问题。