删除句柄 class 中的对象可能会导致未定义的行为
Delete object in handle class potentially causes undefined behavior
我有以下一段代码(来自 Koening & Moo Accelerated C++ 第 255 页),它定义了一个通用句柄 class Handle
。 Handle
用于管理对象的内存:
#include <iostream>
#include <stdexcept>
///Handle
template <class T>
class Handle
{
public:
Handle() : p(0) {}
Handle &operator=(const Handle &);
T *operator->() const;
~Handle() { delete p; }
Handle(T *t) : p(t) {}
private:
T *p;
};
template <class T>
Handle<T> &Handle<T>::operator=(const Handle &rhs)
{
if (&rhs != this)
{
delete p;
p = rhs.p ? rhs.p->clone() : 0;
}
return *this;
};
template <class T>
T *Handle<T>::operator->() const
{
if (p)
return p;
throw std::runtime_error("error");
};
class test_classA
{
friend class Handle<test_classA>;
private:
virtual test_classA *clone() const { return new test_classA; }
public:
virtual void run() { std::cout << "HiA"; }
};
class test_classB : public test_classA
{
private:
virtual test_classB *clone() const { return new test_classB; }
public:
virtual void run() { std::cout << "HiB"; }
};
int main()
{
Handle<test_classA> h;
h = new test_classA;
h->run();
return 0;
}
当我使用 g++ -o main main.cpp -Wall
编译它时,我收到警告:
warning: deleting object of polymorphic class type ‘test_classA’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor]
~Handle() { delete p; }
我不太明白这个警告。句柄class在析构函数中自动删除了指针*p
而不管其类型,那么潜在的陷阱在哪里呢?
你的警告说明了一切。您的 class A
是多态的,但析构函数是非虚拟的。当基 class 没有虚析构函数时,通过指向基 class 的指针删除派生 class 的对象是未定义的行为。
在你的具体例子中没有未定义的行为,因为你没有派生的对象class,但编译器可能无法确定它,所以无论如何它都会警告你。
问题是如果处理的对象是模板实例化类型的子类,会发生错误删除。
在您的情况下,如果您的 Handle<test_classA> h;
将处理类型为 test_classB
的对象...
句柄 h
的类型为 Handle<test_classA>
,因此它将存储指向 test_classA
的指针并调用 test_classA
的析构函数。但是,您可以在句柄中存储指向 test_classB
的指针,在这种情况下,不会调用 test_classB
析构函数,因为 test_classA
析构函数不是虚拟的:
h = static_cast< test_classA * >(new test_classB);
另请注意,此 Handle
class 名称选择不当,它本质上是一种 class.
的智能指针
在 C++ 中,如果您有一个基础 class(此处为 test_classA
),而其他 class 派生自它(此处为 test_classB
),您如果这些指针实际上可能指向类型 test_classB
的对象,则必须小心删除类型 test_classA
的指针。请注意,您在此处编写的代码中正是这样做的。
如果你做这样的事情,你需要给你的基础 class (test_classA
) 一个虚拟析构函数,像这样:
class test_classA {
public:
virtual ~test_classA() = default;
// ...
};
这样,当 C++ 尝试删除 test_classA
类型的指针时,它知道所讨论的指针可能实际上并不指向 test_classA
对象,并且会正确调用正确的析构函数。
顺便说一句,这个问题完全独立于您的包装器类型。你可以通过写
得到同样的问题
test_classA* ptr = new test_classB;
delete ptr; // <--- Warning! Not good unless you have a virtual dtor.
警告是因为您的基 class' 析构函数不是虚拟的。如果你想多态地使用你的class(例如创建一个带有基本class指针的向量,其中指向的对象是派生的class)你会有未定义的行为。
另一件需要提及的事情是,您将句柄 class 声明为 class test_classA 的好友,以便获得对克隆功能的访问权限。请注意,friend 关键字不可传递,因此在派生的 class 中,句柄 class 无法访问克隆函数。
最后你的克隆函数我不是很清楚。先看主要功能:
Handle<test_classA> h;
h = new test_classA
- 你用它的默认构造函数实例化一个 class 句柄,将 p 初始化为空指针(顺便说一句,如果你使用 C++11,最好用 nullptr 而不是 0 或空)
- 在下一行中调用实例 h 的运算符=。在这里,您的 operator= 等待句柄 class。所以表达式 new test_classA 将隐式调用你的 Handle class 构造函数
Handle(T *t) : p(t) {}
。然后这将在您的 operator= 函数中使用,您可以在其中检查 rhs.p 是否为 NULL。因为它不是空的,所以你将调用克隆函数(你写的等同于 (rhs.p)->clone 所以它不是调用的 Handle class' operator-> 而是指针 p)这将在 HEAP 中再次创建内存。
我想这不是你想要的。
我有以下一段代码(来自 Koening & Moo Accelerated C++ 第 255 页),它定义了一个通用句柄 class Handle
。 Handle
用于管理对象的内存:
#include <iostream>
#include <stdexcept>
///Handle
template <class T>
class Handle
{
public:
Handle() : p(0) {}
Handle &operator=(const Handle &);
T *operator->() const;
~Handle() { delete p; }
Handle(T *t) : p(t) {}
private:
T *p;
};
template <class T>
Handle<T> &Handle<T>::operator=(const Handle &rhs)
{
if (&rhs != this)
{
delete p;
p = rhs.p ? rhs.p->clone() : 0;
}
return *this;
};
template <class T>
T *Handle<T>::operator->() const
{
if (p)
return p;
throw std::runtime_error("error");
};
class test_classA
{
friend class Handle<test_classA>;
private:
virtual test_classA *clone() const { return new test_classA; }
public:
virtual void run() { std::cout << "HiA"; }
};
class test_classB : public test_classA
{
private:
virtual test_classB *clone() const { return new test_classB; }
public:
virtual void run() { std::cout << "HiB"; }
};
int main()
{
Handle<test_classA> h;
h = new test_classA;
h->run();
return 0;
}
当我使用 g++ -o main main.cpp -Wall
编译它时,我收到警告:
warning: deleting object of polymorphic class type ‘test_classA’ which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor]
~Handle() { delete p; }
我不太明白这个警告。句柄class在析构函数中自动删除了指针*p
而不管其类型,那么潜在的陷阱在哪里呢?
你的警告说明了一切。您的 class A
是多态的,但析构函数是非虚拟的。当基 class 没有虚析构函数时,通过指向基 class 的指针删除派生 class 的对象是未定义的行为。
在你的具体例子中没有未定义的行为,因为你没有派生的对象class,但编译器可能无法确定它,所以无论如何它都会警告你。
问题是如果处理的对象是模板实例化类型的子类,会发生错误删除。
在您的情况下,如果您的 Handle<test_classA> h;
将处理类型为 test_classB
的对象...
句柄 h
的类型为 Handle<test_classA>
,因此它将存储指向 test_classA
的指针并调用 test_classA
的析构函数。但是,您可以在句柄中存储指向 test_classB
的指针,在这种情况下,不会调用 test_classB
析构函数,因为 test_classA
析构函数不是虚拟的:
h = static_cast< test_classA * >(new test_classB);
另请注意,此 Handle
class 名称选择不当,它本质上是一种 class.
在 C++ 中,如果您有一个基础 class(此处为 test_classA
),而其他 class 派生自它(此处为 test_classB
),您如果这些指针实际上可能指向类型 test_classB
的对象,则必须小心删除类型 test_classA
的指针。请注意,您在此处编写的代码中正是这样做的。
如果你做这样的事情,你需要给你的基础 class (test_classA
) 一个虚拟析构函数,像这样:
class test_classA {
public:
virtual ~test_classA() = default;
// ...
};
这样,当 C++ 尝试删除 test_classA
类型的指针时,它知道所讨论的指针可能实际上并不指向 test_classA
对象,并且会正确调用正确的析构函数。
顺便说一句,这个问题完全独立于您的包装器类型。你可以通过写
得到同样的问题test_classA* ptr = new test_classB;
delete ptr; // <--- Warning! Not good unless you have a virtual dtor.
警告是因为您的基 class' 析构函数不是虚拟的。如果你想多态地使用你的class(例如创建一个带有基本class指针的向量,其中指向的对象是派生的class)你会有未定义的行为。
另一件需要提及的事情是,您将句柄 class 声明为 class test_classA 的好友,以便获得对克隆功能的访问权限。请注意,friend 关键字不可传递,因此在派生的 class 中,句柄 class 无法访问克隆函数。
最后你的克隆函数我不是很清楚。先看主要功能:
Handle<test_classA> h;
h = new test_classA
- 你用它的默认构造函数实例化一个 class 句柄,将 p 初始化为空指针(顺便说一句,如果你使用 C++11,最好用 nullptr 而不是 0 或空)
- 在下一行中调用实例 h 的运算符=。在这里,您的 operator= 等待句柄 class。所以表达式 new test_classA 将隐式调用你的 Handle class 构造函数
Handle(T *t) : p(t) {}
。然后这将在您的 operator= 函数中使用,您可以在其中检查 rhs.p 是否为 NULL。因为它不是空的,所以你将调用克隆函数(你写的等同于 (rhs.p)->clone 所以它不是调用的 Handle class' operator-> 而是指针 p)这将在 HEAP 中再次创建内存。
我想这不是你想要的。