【问题标题】:Deleting templated class pointers in a non-templated class destructor?在非模板类析构函数中删除模板类指针?
【发布时间】:2018-03-24 16:20:04
【问题描述】:

下面的代码test-templated-destructor.cpp 复制了我正在使用的库的组织结构。我正在使用:

$ cat /etc/issue
Ubuntu 14.04.5 LTS \n \l
$ g++ --version
g++ (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
$ g++ -std=c++14
g++: error: unrecognized command line option ‘-std=c++14’
g++: fatal error: no input files
compilation terminated.
$ g++ -std=c++11
g++: fatal error: no input files
compilation terminated.

有:

  • 基类AA,以及从它派生的类BBCC
  • 抽象类AAInstancer,以及从它派生的类AAInstancerTemplated模板化
  • AAHandler,它有一个模板化函数addTemplatedObject,它将AAInstancer*指向new AAInstancerTemplated<T>()对象的指针存储在类的map属性中
  • main()中,一个AAHandler对象被实例化,.addTemplatedObject<BB>("BB");调用它

如果我对此运行valgrind,它会报告:

==21000== 43 (16 direct, 27 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==21000==    at 0x4C2B0E0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==21000==    by 0x40141B: void AAHandler::addTemplatedObject<BB>(std::string) (test-templated-destructor.cpp:64)
==21000==    by 0x40113E: main (test-templated-destructor.cpp:82)

我认为问题在于我们在addTemplatedObject() 中使用了new,因此我们应该在程序退出时相应地删除它——但这并没有完成,这就是泄漏的原因。

所以我想,写一个遍历instancers映射的迭代器,以及deletes这些指针在AAHandler的析构函数中,但我不能:

  • 如果我写:
  ~AAHandler() {
    cout << "  (running AAHandler destructor)" << endl;
    map<string, AAInstancer*>::iterator it;
    for ( it = instancers.begin(); it != instancers.end(); it++ ) {
      delete it->second;
    }
  }

...然后我开始编译:

$ g++ -g -Wall test-templated-destructor.cpp -o test-templated-destructor.exe
test-templated-destructor.cpp: In destructor ‘AAHandler::~AAHandler()’:
test-templated-destructor.cpp:60:18: warning: deleting object of abstract class type ‘AAInstancer’ which has non-virtual destructor will cause undefined behaviour [-Wdelete-non-virtual-dtor]
       delete it->second;
                  ^

... 听起来不错 - AAInstancer 没有定义析构函数,因此编译器可能自动添加为非虚拟的,导致此警告(尽管通过 valgrind 运行它会显示不再检测到泄漏)。

  • 如果我写:
  template <class T>
  ~AAHandler() {
      cout << "  (running AAHandler destructor)" << endl;
      map<string, AAInstancer*>::iterator it;
      for ( it = instancers.begin(); it != instancers.end(); it++ ) {
        delete (AAInstancerTemplated<T>*)it->second;
      }
    }

...希望如果我们用一些模板调用addTemplatedObject(无论如何它不会),这个析构函数会被调用,编译失败:

$ g++ -g -Wall test-templated-destructor.cpp -o test-templated-destructor.exe && ./test-templated-destructor.exe
test-templated-destructor.cpp:57:14: error: destructor ‘AAHandler::~AAHandler()’ declared as member template
   ~AAHandler() {
              ^

...这也是有道理的:AAHandler 是一个非模板类,所以它的析构函数可能也不应该被模板化。

那么,是否可以为AAHandler 编写一个析构函数,它会将delete 的所有new 指针放在其instancers 中,无论它们是用哪个模板实例化的 - 使用最少(或最好,否)对现有代码的更改?

test-templated-destructor.cpp

// g++ -g -Wall test-templated-destructor.cpp -o test-templated-destructor.exe && ./test-templated-destructor.exe
// valgrind --leak-check=yes ./test-templated-destructor.exe

#include <iostream>
#include <map>
using namespace std;

class AA {
public:
  string myname;
  AA() {
    myname = "";
    cout << "  AA instantiated\n";
  }
};


class BB : public AA {
public:
  string mystuff;
  BB() {
    mystuff = "";
    cout << "  BB instantiated\n";
  }
};

class CC : public AA {
public:
  string mythings;
  CC() {
    mythings = "";
    cout << "  CC instantiated\n";
  }
};

class AAInstancer
{
public:
    virtual AA* createInstance() = 0;
    string tagName;
};

template <class T>
class AAInstancerTemplated: public AAInstancer
{
public:
    AA* createInstance() {
        return new T();
    }
};


class AAHandler
{
public:
    ~AAHandler() { }
    AAHandler() { }
    static map<string, AAInstancer*> instancers;

    template <class T>
    static void addTemplatedObject(string tagName) {
        AAInstancer* instancer = new AAInstancerTemplated<T>();
        instancer->tagName = tagName;
        instancers[tagName] = instancer;
    }

  AAHandler* get() {
    if(singleton == NULL)
      singleton = new AAHandler();
    return singleton;
  }
private:
    static AAHandler* singleton;
};
map<string, AAInstancer*> AAHandler::instancers;



int main()
{
  AAHandler aah;
  aah.addTemplatedObject<BB>("BB");

  cout << "Address of aah: " << static_cast<void*>(&aah) << endl;
  return 0;
}

【问题讨论】:

  • 为什么不用std::unique_ptr&lt;AAInstancer&gt; 而不是AAInstancer*
  • map&lt;string, unique_ptr&lt;AAInstancer&gt;&gt; 怎么样,那你什么都不用做?

标签: c++ templates


【解决方案1】:

AAInstancer 需要一个虚拟析构函数。 如果它不需要正文,您可以默认它。

virtual ~AAInstancer() = default;

【讨论】:

  • 谢谢@John - 我试过这个:"warning: defaulted and deleted functions only available with -std=c++11 or -std=gnu++11";使用g++ -g -std=c++11 -Wall ... 编译,不幸的是,valgrind 检测到相同的泄漏
  • @sdaau 你的编译器不支持这些建议的标志吗?你试过这样做吗?
  • @user0042 - 是的,但不是默认情况下 - 这就是为什么我说“g++ -g -std=c++11 -Wall编译...通过”, - 所以编译没问题,但仅此更改仍然会泄漏内存。
【解决方案2】:

使用std::unique_ptr&lt;AAInstancer&gt;

map<string, std::unique_ptr<AAInstancer>>

作为成员,而不是自己管理内存。

【讨论】:

  • 谢谢@user0042,std::unique_ptr 也是-std=c++11(否则error: ‘unique_ptr’ is not a member of ‘std’ 即使是#include &lt;memory&gt;);但是当我用static map&lt;string, std::unique_ptr&lt;AAInstancer&gt;&gt; 替换instancers 的声明和定义,并且不改变OP 的任何其他内容时,我得到“In instantiation of ‘static void AAHandler::addTemplatedObject ... -destructor.cpp:87:34: required from here ... -destructor.cpp:68:23: error: no match for ‘operator=’ ....”,意思是现在我必须做:...
  • ...addTemplatedObject我必须使用类似std::unique_ptr&lt;AAInstancer&gt; instancer = std::unique_ptr&lt;AAInstancer&gt;(new AAInstancerTemplated&lt;T&gt;());的东西,结果是“error: use of deleted function ‘std::unique_ptr...”...有什么建议吗?
  • @sdaau 到底删除了哪个函数?也许问另一个问题提供minimal reproducible example,然后我最终可以帮助解决这个错误。
  • 整行是:error: use of deleted function ‘std::unique_ptr&lt;_Tp, _Dp&gt;&amp; std::unique_ptr&lt;_Tp, _Dp&gt;::operator=(const std::unique_ptr&lt;_Tp, _Dp&gt;&amp;) [with _Tp = AAInstancer; _Dp = std::default_delete&lt;AAInstancer&gt;]’,接下来是`instancers[tagName] = instancer;`
【解决方案3】:

好的,终于可以在c++11 下正常编译并且不会泄漏; valgrind 报道:

$ valgrind --leak-check=yes ./test-templated-destructor.exe==22888== Memcheck, a memory error detector
==22888== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==22888== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==22888== Command: ./test-templated-destructor.exe
==22888== 
Address of aah: 0xffefffb3f
  (running AAHandler destructor)
  ~AAInstancerTemplated <2BB> here; tref: 0x5a200b0
    ~AAInstancer here
  ~AAInstancerTemplated <2CC> here; tref: 0x5a201e0
    ~AAInstancer here
==22888== 
==22888== HEAP SUMMARY:
==22888==     in use at exit: 0 bytes in 0 blocks
==22888==   total heap usage: 6 allocs, 6 frees, 198 bytes allocated
==22888== 
==22888== All heap blocks were freed -- no leaks are possible
==22888== 
==22888== For counts of detected and suppressed errors, rerun with: -v
==22888== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

是的,没有泄漏 - 我喜欢 :)

该方法有点经典:使AAInstancerTemplated 保留对通过new 实例化的任何内容的引用(此处为tref),然后为其创建一个析构函数(用于AAInstancerTemplateddeletes这个参考。

请注意,即使在AAHandler 中,我们在instancers 中存储通用指针AAInstancer*,同时我们实例化模板对象(new AAInstancerTemplated&lt;T&gt;();)——现在,有了这个组织,当我们delete it-&gt;second 是输入AAInstancer*,调用正确的模板析构函数。

固定的test-templated-destructor.cpp

// g++ -g -std=c++11 test-templated-destructor.cpp -o test-templated-destructor.exe && ./test-templated-destructor.exe
// valgrind --leak-check=yes ./test-templated-destructor.exe

#include <iostream>
#include <map>
#include <typeinfo>
#include <functional> // note: uses std::function, which is c++11 feature

using namespace std;

class AA {
public:
  string myname;
  AA() {
    myname = "";
    cout << "  AA instantiated\n";
  }
};


class BB : public AA {
public:
  string mystuff;
  BB() {
    mystuff = "";
    cout << "  BB instantiated\n";
  }
};

class CC : public AA {
public:
  string mythings;
  CC() {
    mythings = "";
    cout << "  CC instantiated\n";
  }
};

class AAInstancer
{
public:
  virtual ~AAInstancer() {
    cout << "    ~AAInstancer here" << endl;
  }
  virtual AA* createInstance() = 0;
  string tagName;
};

template <class T>
class AAInstancerTemplated: public AAInstancer
{
public:
  T* tref;
  AA* createInstance() {
    if (tref) delete tref;
    tref = new T();
    return tref;
  }
  ~AAInstancerTemplated() {
    cout << "  ~AAInstancerTemplated <" << typeid(T).name() << "> here; tref: " << static_cast<void*>(&tref) << endl;
    if (tref) delete tref;
  }
};


class AAHandler
{
public:
  ~AAHandler() {
    cout << "  (running AAHandler destructor)" << endl;
    typedef typename map<string, AAInstancer*>::iterator instIterator;
    for ( instIterator it = instancers.begin(); it != instancers.end(); it++ ) {
      delete it->second;
    }
  }
  AAHandler() { }
  static map<string, AAInstancer*> instancers;

  template <class T>
  static void addTemplatedObject(string tagName) {
    AAInstancer* instancer = new AAInstancerTemplated<T>();
    instancer->tagName = tagName;
    instancers[tagName] = instancer;
  }

  AAHandler* get() {
    if(singleton == NULL)
      singleton = new AAHandler();
    return singleton;
  }
private:
  static AAHandler* singleton;
};
map<string, AAInstancer*> AAHandler::instancers;

int main()
{
  AAHandler aah;
  aah.addTemplatedObject<BB>("BB");
  aah.addTemplatedObject<CC>("CC");

  cout << "Address of aah: " << static_cast<void*>(&aah) << endl;
  return 0;
}

【讨论】:

  • 另请注意,这在这个简单的示例中有效,但可能不适用于更复杂的示例,其中不能只是 if (tref) delete tref; in createInstance
猜你喜欢
  • 2016-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-20
相关资源
最近更新 更多