C++支持将对象储存在栈上,但很多情况,对象不能,或不应该存储在栈上。比如:
- 对象很大
- 对象的大小在编译时不能确定
- 对象是函数的返回值,但由于特殊的原因,不应使用对象的值返回
常见情况之一是,在工厂方法或其他面向对象编程的情况下,返回值类型是基类(的指针或引用)。
举例:是对工厂方法的简单演示:
enum class shape_type {
circle,
triangle,
rectangle,
…
};
class shape { … };
class circle : public shape { … };
class triangle : public shape { … };
class rectangle : public shape { … };
shape* create_shape(shape_type type)
{
…
switch (type) {
case shape_type::circle:
return new circle(…);
case shape_type::triangle:
return new triangle(…);
case shape_type::rectangle:
return new rectangle(…);
…
}
}
这个create_shape
方法会返回一个shape对象,对象的实际类型是某个shape的子类,圆/三角形等。在这种情况下,函数的返回值只能是指针或其变体形式。如果返回类型是shape,实际却返回一个circle,编译器不会报错,但结果多半是错的,这种现象就叫做对象切片(object slicing),是C++特有的一种编码错误。这种错误不是语法错误,而是一个对象复制相关的语义错误,也算是C++的一个陷阱,所以要注意这种情况。
那怎样才能确保在使用create_shape
的返回值时不发生内存泄漏呢?
答案就在析构函数和它的栈展开行为上。 只需要把这个返回值存放在一个本地变量里,并确保其析构函数会删除该对象即可。
简单的实现如下:
class shape_wrapper {
public:
explicit shape_wrapper(
shape* ptr = nullptr)
: ptr_(ptr) {}
~shape_wrapper()
{
delete ptr_;
}
shape* get() const { return ptr_; }
private:
shape* ptr_;
};
void foo()
{
…
shape_wrapper ptr_wrapper(
create_shape(…));
…
}
delete
空指针会发生什么?答案就是这是一个合法的空操作。
在new一个对象和delete一个指针时编译器需要干不少的活,大致翻译如下:
// new circle(…)
{
void* temp = operator new(sizeof(circle));
try {
circle* ptr =
static_cast<circle*>(temp);
ptr->circle(…);
return ptr;
}
catch (...) {
operator delete(ptr);
throw;
}
}
new
的时候会先分配内存(失败时整个操作失败并向外抛出异常,通常是bad_alloc),然后在这个结果指针上构造对象;构造成功后则new操作整体完成,否则释放刚分配的内存并继续向外抛构造函数产生的异常。
if (ptr != nullptr) {
ptr->~shape();
operator delete(ptr);
}
delete
时先判断指针是否为空,在指针不为空时调用析构函数并释放之前分配的内存。
回到 shape_wrapper 和它的析构行为。在析构函数里做必要的清理工作,这就是 RAII 的基本用法。这种清理并不限于释放内存,也可以是:
- 关闭文件(fstream的析构就会这么做)
- 释放同步锁
- 释放其他重要的系统资源
例如,我们应该使用:
std::mutex mtx;
void some_func()
{
std::lock_guard<std::mutex> guard(mtx);
// 做需要同步的工作
}
而不是:
std::mutex mtx;
void some_func()
{
mtx.lock();
// 做需要同步的工作……
// 如果发生异常或提前返回,
// 下面这句不会自动执行。
mtx.unlock();
}
标签:对象,切片,shape,new,circle,type,ptr
From: https://www.cnblogs.com/whiteBear/p/16727436.html