删除句柄类中的对象可能会导致未定义的行为

时间:2017-09-08 20:30:43

标签: c++ pointers virtual-destructor

我有以下代码(来自Koening& Moo Accelerated C ++ 第255页),它定义了一个通用句柄类HandleHandle用于管理对象的内存:

#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; }

我不太明白这个警告。 handle类自动删除析构函数中的指针*p,无论其类型如何,那么潜在的陷阱在哪里?

5 个答案:

答案 0 :(得分:1)

你的警告说明了一切。您的类admin.StackedInline是多态的,但析构函数是非虚拟的。当基类没有虚析构函数时,通过指向基类的指针删除派生类的对象是未定义的行为。

在您的具体示例中,没有未定义的行为,因为您没有派生类的对象,但编译器可能无法确定它,因此无论如何都会向您发出警告。

答案 1 :(得分:1)

问题是,如果处理的对象是模板instanciation类型的子类,则会发生错误的删除。

在您的情况下,如果您的Handle<test_classA> h;将处理test_classB类型的对象,则会发生...

答案 2 :(得分:1)

句柄HttpServletRequest的类型为@Override public void configure(HttpSecurity http) { http.authorizeRequests() .antMatchers("/", "/signup").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/home", true) .permitAll(); } ,因此它会存储指向h的指针并调用Handle<test_classA>的析构函数。但是,您可以在句柄中存储指向test_classA的指针,在这种情况下test_classA析构函数不会被调用,因为test_classB析构函数不是虚拟的:

test_classB

另请注意,此test_classA类的名称选择不当,它本质上是一种智能指针类。

答案 3 :(得分:1)

在C ++中,如果你有一个基类(这里,test_classA),其中有一些派生自它的类(这里是test_classB),你必须小心删除test_classA类型的指针。 1}}如果这些指针实际上可能指向test_classB类型的对象。请注意,您在此处编写的代码中正是这样做的。

如果您执行此类操作,则需要为基类(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.

答案 4 :(得分:0)

警告是由于您的基类的析构函数不是虚拟的。如果你想以多态方式使用你的类(例如,创建一个带有基类指针的向量,其中指向的对象是派生类),你将会有未定义的行为。

另外需要提及的是,您将Handle类声明为类test_classA的朋友,以便获得对clone功能的访问权限。请注意,friend关键字不可传递,因此在派生类中,Handle类无法访问克隆函数。

最后你的克隆功能对我来说不太清楚。我们来看看主要功能:

Handle<test_classA> h;
h = new test_classA
  1. 使用默认构造函数实例化一个Handle类,它将p初始化为nullpointer(顺便说一句,如果使用C ++ 11,使用nullptr而不是0或NULL初始化它会好得多)
  2. 在下一行中,您调用实例h的operator =。在这里,你的operator =等待一个Handle类。所以表达式new test_classA将隐式调用你的Handle类构造函数Handle(T *t) : p(t) {}。然后,这将在您的operator =函数中使用,您可以在其中检查rhs.p是否为NULL。因为它不是null你将调用克隆函数(你写的等价于(rhs.p) - &gt;克隆所以它不是Handle类'operator-&gt;它被调用但指针p)将创建一个在HEAP再次记忆。
  3. 我认为这不是你想要的。